For a normal Javascript object on $scope, you might use ngShow with a div to make the div's presence depend on the existence of the object.
$scope.myObj = null;
// And then later
$scope.myObj = {arbitrary:{number:{of:"objects"}}};
...
<div ng-show="myObj">{{myObj}}</div>
But if you tie an object to a $firebaseObject
var fbObjRef = new Firebase(FirebaseURL).child("fbObj")
$scope.myObj = $firebaseObject(fbObjRef);
Then your ngShow won't work because a reference that is empty in Firebase will still be represented client side by an object that looks like:
{
"$id":"fbObj",
"$priority":null,
"$value":null
}
And then later if non-primitive content is set at that reference, the $value key disappears and is replaced by the content:
{
"$id":"fbObj",
"$priority":null,
"someKey":{
"anotherKey":"someValue"
}
}
What's the best way to check against the existence of the $firebaseObject? Here's what I'm doing but it feels clunky. Maybe this is an opportunity to add an $exists key to the AngularFire API.
<div ng-show="myObjExists()">{{myObj}}</div>
...
$scope.myObjExists = function(){
return !(
$scope.myObj.hasOwnProperty("$value")
&& $scope.myObj.$value === null
);
};
Related
I'm using ngrx/component-store and loving it so far. Having prior store knowledge building my own simple ones, the only real headache I've had so far is when I've had to update an array and figured out I have to always create a new one for the internal compare() pipe to realize the array got updated.
Anyway, reading through the documentation it talks about updater methods and patchState. To me they do exactly the same thing, but their creation is slightly different. You would call patchState inside of a method while this.updater() returns a method giving you a function you can expose in your service. Anytime I'm updating my state it's always after a network call. I assume there are plenty of scenarios where you'd want to update your state without a network call so this is why you would want to have an updater available to your component to call. The question is if an updater and patchState are really doing the same thing then is it a better practice to call an updater in an effect or use patchState, or maybe am I putting too much logic in my effect?
On a side note, the docs say an updater method is supposed to be a pure function. If you're using it to your push an object onto an array then is it really pure?
// adding the selectors so people know what components are subscribing to
readonly approvals$ = this.select(state => state.requestApprovals);
readonly registration$ = this.select(state => state);
readonly updateAssessment = this.effect(($judgement: Observable<{id: string, isApproved: boolean}>) => {
return $judgement.pipe(
switchMap((evaluation) => {
const state = this.get();
return this.requestApproval.patch(state.id, state.companyName, evaluation.id, evaluation.isApproved).pipe(
tapResponse(
(result) => {
// is it better to call patchState()?
this.patchState((state) => {
for(let i = 0; i < state.requestApprovals.length; i++) {
if(state.requestApprovals[i].id == result.id) {
state.requestApprovals[i].isApproved = result.isApproved;
}
}
// the take away is you must assign a whole new array object when you update it.
state.requestApprovals = Object.assign([], state.requestApprovals);
return state;
});
// or this updater?
// this.applyDecisionPatch(evaluation);
},
// oh look! another updater reassigning my array to the state so
// it propagates to subscribers to reset the UI
() => { this.reverseDecision(); }
)
);
})
);
});
// this is private to make sure this can only be called after a network request
private readonly applyDecisionPatch = this.updater((state, value: {id: string, isApproved: boolean}) => {
for(let i = 0; i < state.requestApprovals.length; i++) {
if(state.requestApprovals[i].id == value.id) {
state.requestApprovals[i].isApproved = value.isApproved;
}
}
state.requestApprovals = Object.assign([], state.requestApprovals);
return state;
});
Note: There's no tag for ngrx-component-store so couldn't tag it.
An updater can be compared to a reducer.
All the options to modify the state should change it in an immutable way.
A library like ngrx-immer can be used to make this easier.
The main difference is that updater receives the current state, and you can change the state based on it. E.g. a conditional update, or can be used with #ngrx/entity
While with setState and patchState, you just set state properties.
setState updates the whole state object, whereas patchState only sets the given properties and doesn't touch the rest of the state object.
These two methods are also easier to use when you just want to set the state, because you don't have to create an updater function.
To answer the side question, push is not immutable. Instead of creating a new instance, it updates the array instance.
Why even with an IF check, Flow still complains about a possibly null value
if(document && document.getElementById("myID") && document.getElementById("myID").offsetWidth){
console.log(document.getElementById("myID").offsetWidth);
}
Gives this error
^ property `offsetWidth`. Property cannot be accessed on possibly null value
Flow has no way to know that the success of first call to getElementById means that the later ones will also succeed. For all it knows, reading the offsetWidth property could cause getElementById to start returning null the next time it is called.
You'll need to store the value, e.g.
const myIdEl = document && document.getElementById("myID");
if(myIdEl && myIdEl.offsetWidth) {
console.log(myIdEl.offsetWidth);
}
this way there is no way for myIdEl to become null after it has been referenced.
For HTMLElement (and extensions of HTMLElement like VideoHTMLElement) in FlowType, I'd recommend using instanceof to validate the Type and to validate that it's not null.
Also, I don't believe you need to check if document exists, that is defined globally in flow (1)*
<HTMLElement> Example
const myIdEl: ?HTMLElement = document.getElementById('myID');
if (myIdEl instanceof HTMLElement) {
// continue
console.log(myIdEl.offsetWidth);
}
<HTMLSelectElement> Example
const selectEl: ?HTMLElement = document.getElementById('someSelectElement');
// Checks correct type (!null !undefined come for free)
if (selectEl instanceof HTMLSelectElement) {
const selectedVal = selectEl.options[selectEl.selectedIndex].value;
}
<HTMLVideoElement> Example using invariant
import invariant from 'invariant';
const videoContent = document.getElementById('video-player');
invariant(videoContent instanceof HTMLVideoElement, 'No video element');
// do stuff with video api
videoContent.volume = 0;
videoContent.plause();
https://github.com/facebook/flow/blob/f3f29f7fd8c5aa73ac5a8a546ccfbc29cd7505fe/lib/dom.js#L1288
I'm trying to perform a custom sort using a comparator function from within a template helper in Meteor.
Here is my template helper:
Template.move_list.helpers({
assets() {
return Assets.find({}, { sort: sortFunction });
}
});
And here is the comparator function:
const sortFunction = function (doc1, doc2) {
const barcodes = Session.get('barcodesArray');
if (barcodes.indexOf(doc1.barcode) === -1 || barcodes.indexOf(doc2.barcode) === -1) {
return 0;
}
let last = null;
_.each(barcodes, function (barcode) {
if (barcode === doc1.barcode) last = doc1.barcode;
if (barcode === doc2.barcode) last = doc2.barcode;
});
return last === doc1.barcode ? 1 : -1;
}
Error
When the page loads, the following error is returned:
Exception in template helper: Error: Match error: Failed Match.OneOf, Match.Maybe or Match.Optional validation
I put a breakpoint in chrome into the sortFunction, however the function was never entered and the breakpoint never reached.
Of course, the error is not throw when I remove sort.
References
This feature is not very well documented, however here is the relevant part of the docs:
For local collections you can pass a comparator function which receives two document objects, and returns -1 if the first document comes first in order, 1 if the second document comes first, or 0 if neither document comes before the other. This is a Minimongo extension to MongoDB.
And the commit by mitar adding the functionality, with example code from the test:
var sortFunction = function (doc1, doc2) {
return doc2.a - doc1.a;
};
c.find({}, {sort: sortFunction})
Can anyone make sense of this error?
Edit:
This issue should be resolved in Meteor >= v1.3.3.1.
Local collections (i.e, client-side and in-memory server-side collections) will allow to pass a function as the sort clause.
The error comes from the mongo package, where the spec does not allow sort to be a function.
#mitar changed LocalCollection in the minimongo package. LocalCollection is part of the Mongo.Collection object on the client (its _collection attribute), but queries are still checked according to the original mongo spec. I believe this to be a bug, as the spec was not updated to reflect the change.
To overcome this (in the meantime), either have the function accept a sub-field, such that the sort value is an object:
var sortFunction = function (x, y) {
return x - y;
};
c.find({}, {sort: {a: sortFunction}});
or use the c._collection.find() instead, which will work (as far as I can tell), except it will not apply any transformations defined for the collection.
var sortFunction = function (doc1, doc2) {
return doc2.a - doc1.a;
};
c._collection.find({}, {sort: sortFunction});
This code passes the flow check:
/* #flow */
function test (list: ?Array<string>): Promise<number> {
if(list !== null && list !== undefined) {
return Promise.resolve(list.length)
} else {
return Promise.resolve(0)
}
}
console.log(test(null))
Whereas the following gets a null check error
/* #flow */
function test (list: ?Array<string>): Promise<number> {
if(list !== null && list !== undefined) {
return Promise.resolve().then(() => list.length)
} else {
return Promise.resolve(0)
}
}
console.log(test(null))
error:
property `length`. Property cannot be accessed on possibly null value
Clearly list cannot be null so there must be something about the code structure that makes flow unable to recognise this.
I would like to understand what limitation I am hitting and how I can work around it. Thanks!
Basically, Flow doesn't know that your type refinement (null check) will hold at the time when () => list.length is executed. Inside that callback Flow only looks at the type of list – which says it can be null.
The difference between first and second snippet is that in the second snippet list is crossing a function boundary – you're using it in a different function than where you refined its type.
One solution is to extract list.length into a variable, and use that variable in the callback.
var length = list.length;
return Promise.resolve().then(() => length)
This might also work:
var list2: Array<string> = list;
return Promise.resolve().then(() => list2.length)
Note that this problem exists even for immediately invoked callbacks, e.g. when using map or forEach. There is an issue on flow's github about this, but I couldn't find it after a quick search.
I was wondering if there was a way to have knockout check to see if data is null before it tries to put it into an observable?
Right now I do this:
if (!data.Filename) {
this.FileName = ko.observable("");
}
else {
this.FileName = ko.observable(data.Filename);
}
Otherwise a null value in the data will cause the entire property not to show up. Is there a way to use extenders or something that I can add a null check to without having to do this with every property? My data has nulls in random places that I can't control and I don't want the property not to show up because one row in the dataset has a null value for that property.
Seems like there should be a better way to do this.
heh
There are a number of ways to do this. What I would do is
var self = this;
self.fileName = ko.observable(data.Filename);
self.fileNameComputed = ko.computed(function(){
return self.fileName() || ""
});
Then, in your mark up reference the computed instead if the observable.
In Javascript there are other patterns available to do this.
The first, and simplest, is akin to the ?? operator in C#:
function ViewModel(data) {
data = data || {};
this.Filename .observable(data.Filename || "");
}
The || operator will return the left operand unless it is falsy, then it'll fall back to the second argument. My example above will:
Make sure data itself is "at least" an empty object (where .Filename would be undefined);
Make sure that the input to ko.observable(...) is "at least" the empty string.
A second option would be to use default options. An example that utilizes jQuery to merge input data and default options:
var defaultData = {
Filename: "enter-a-file" // could also be empty string of course!
};
function ViewModel(data) {
var dto = $.extend({}, defaultData, data);
this.Filename = ko.observable(dto.Filename);
}
This will "fold" data into defaultData, and fold that result into an empty, fresh object, making sure the dto variable holds the merged result. The rest of your function can then safely assume a fully populated input variable.
The third and final option I'll mention is a remix of QBM5's answer, but I agree with the commenter's there that if you can use a pureComputed (which, in your example, is perfectly fine), you probably should:
function ViewModel(data) {
var self = this;
data = data || {};
this.Filename = ko.observable(data.Filename);
this.FilenameText = ko.pureComputed(function() {
return self.Filename() || "";
});
}
PS. You didn't have the underlying issue you mention because you spell FileName and Filename with different capitalization on this and data respectively, didn't you? ;-)