Newbee in Angular, how to use dictionaries? - arrays

I'm a C# programmer trying to learn Angular and Ionic.
I'd like to do something as we usually do in C# with dictionaries:
export class HomePage {
private times = [
{key: 'clockOn', value: '09:00'},
{key: 'lunchTime', value: '12:00'},
{key: 'backToWork',value: '13:00'},
{key: 'clockOff', value: '18:00'}
];
}
getHour(name: string) {
if(this.times[name] === 'clockOn'){
console.log('time: ' + this.times[name].value);
}
On HTML
<ion-datetime class="time" id="coff" displayFormat="HH:mm"
[(ngModel)]="cOff" (ionChange)="getHour('clockOn')">
</ion-datetime>
And on Chrome console I see:
clockOnundefined
First of all, is this the correct sintax to access the member 'value'?
this.times[0].value
I want to know if is it possible to access an array element using a string as indexer (key) like we do in C# with dictionaries
For example:
this.times['clockOn'].value //would bring me its value like '09:00'
or the only way to access a element is using numbers as indexers? Like below
this.times[0].key // bring me 'clockOn'
this.times[0].value // bring me '09:00'

Dictionary is Angular/ JavaScript does not work same as C#.
So your code:
this.times['clockOn'].value
this will not work.
You will have to use
this.times[0].key // bring me 'clockOn'
this.times[0].value // bring me '09:00'
If you want access with key like array[key] returning the value run the loop in this manner:
<td *ngFor="(key, value) in dictionary">
{{value}}
</td>
Hope it helps.

Personally I would just modify your data structure, if they are just hardcoded values.
private times = {
clockOn: '09:00',
lunchTime: '12:00',
backToWork: '13:00',
clockOff: '18:00'
};
getHour(key: string)
{
if (this.times[name] === 'clockOn')
{
console.log('time: ' + this.times[name].value);
}
}
<span *ngFor="(key, value) in times" *ngClick=getHour(key)>{{value}}</span>
Having an object with unique keys where you can use bracket notation to look up the desired item is more effective as it allows for quick simple look ups.
If you wanted to use the original data structure you would have to pass in the index of the repeat item so you can access the correct item in the array.
so your mark up would look like this:
<span *ngFor="time in times" let i=index; *ngClick=getHour(i)>{{value}}</span>
getHour(index: number)
{
if (this.times[index].key === 'clockOn')
{
console.log('time: ' + this.times[index].value);
}
}

Related

Receive [object Object] when trying to log Reactive Form on Angular

I am currently working in a component which consists in a reactive form, and every field in the form returns an array of objects, and thet array of objects is different for every field. This is important because, when the form is filled, I need to create an URL in order to call an API with that data.
I have already done 2 methods that generate that URL, but they only work when the input is a text field, or when the data for that field is another type other than an array of objects. But all my inputs are multiselect using PrimeNG, so they return an array of objects. I show one input, but all of them are similar.
component.html
<span>Field 1: </span>
<span>
<p-multiSelect
[options]="options"
defaultLabel="Placeholder"
optionLabel="optionLabel"
display="chip"
formControlName="formControlName1"
>
</p-multiSelect>
</span>
The previous code returns this:
formControlName1: Array(2)
0: {foo: 'bar 1'}
1: {foo: 'bar 2'}
length: 2
[[Prototype]]: Array(0)
What I have tried so far are those two methods:
component.ts
onSubmit(form: any) {
const tree = this.router.createUrlTree([], { queryParams: form });
console.log(this.serializer.serialize(tree));
}
generateUrl() {
for (const control in this.myForm.controls) {
const val = this.myForm.controls[control].value;
if (val !== null) {
this.stringFinal += `${control}=${val}&`;
}
}
console.log(this.stringFinal);
}
Those two methods return mostly the same, but as I sais previously, they work when the input data is a text, not an array.
So my question is, how to access the the array of objects, and obtain every single data for the foo field.
I managed to make it work:
generateUrl() {
for (const control in this.myForm.controls) {
const val = this.myForm.controls[control].value;
if (val !== null) {
val.forEach((element: any) => {
this.stringFinal += `${control}=${element}&`;
});
}
}
console.log(this.stringFinal);
}

How to force Object type returned from an Observable to an array of objects in Angular?

I'm obtaining a response using GET like this.
constructor(private http: HttpClient) { }
items: Item[];
stuff: any[];
ngOnInit() {
const url = ...;
this.http.get(url)
.subscribe(next => {
console.log(next);
console.log(next.length);
this.items = next;
this.stuff = next;
});
}
I can see that there's 6 elements and what they are (checking the console). However, neither of the fields seem to the the array. I'm trying to iterate out the elements using the ngFor directive but seeing nothing or just a single line.
<div ngFor="let item of items">
->{{item?.id}}
</div>
<div ngFor="let item of stuff">
->{{item?.id}}
</div>
I know that I can resolve it by exposing the data through a service like this but partly, I want to do a quicky here and learn how to do it; partly, I'm going to have the very same problem in the service code instead.
I've tried using map and forEach on the next value but I got the error saying that Object isn't an array. The IDE suggested adding the work array to the syntax so it becomes next.array.forEach but that didn't even got executed, producing a lot of red ink.
What should I do? (Not sure what to google for at this stage.)
donkeyObservArray: Observable<Donkey[]>;
donkeyArray: Array<Donkey>;
this.donkeyObservArray.subscribe(donk=> {
this.donkeyArray = donk;
console.log(this.donkeyArray);
and to be happy...
Or get typed:
donkey: Donkey= null;
getDonkey(): Donkey{
this.donkey = new Donkey();
this._http.get<Donkey>(...your ws link)
.subscribe(data => {
this.donkey = data;
You can either convert the object to an array before passing to template
object-2-array-angular-4
or use a custom pipe to transform object to array

How does filter work in AngularJS?

I have a table generated with ng-repeat (from an objects' array).
I would like to filter it with a search text field.
Objects contained in my array has got deep properties.
I don't know why and how, but the filter is only working on email field, which is as deep as other properties.
I'm using this search form :
<input type="text" name="search" ng-model="searchText" />
...
<tr ng-repeat="x in obj | filter:searchText track by $index">
...
</tr>
plunker
EDIT :
This answer helps me to understand why it's not working.
Someone knows how I can bypass the $ verification in filter ?
I'm using $ because I'm following the Google Contact API format.
You can check the source code of ngFilter here
It is set to ignore keys starting with $ as it's a prefix used by AngularJS for public ($) and private ($$) properties.
$ is a prefix used by Angular internal properties. For technical reasons, Angular prevents you to use it. Here is a workaround to deal with $ properties names without changing your JSON object:
You can iterate in ng-repeat over Object.keys($scope.object) instead $scope.object.
Demo on JSFiddle
Since it is clear that we can change neither third party API nor AngularJS library code, we could go for modifying the object keys to not have $ in the beginning. But, since the data has so many of them at multiple level, let's do it recursively! :)
Here's how. I would remap each object in $scope.obj array to call a function:
$scope.obj = $scope.obj.map(function(cur) {
return renameKey(cur)
})
Now, inside renameKey, it would check whether it's an Array or Object using helper functions and call itself recursively while replacing the keys prepending x for the strings starting with $
function renameKey(cur) {
if(isArray(cur)) {
cur.forEach(function(obj) {
obj = renameKey(obj)
})
} else if (isObject(cur)) {
for (let key in cur) {
if(key.charAt(0) === '$') {
cur['x'+key] = cur[key];
delete cur[key];
}
cur[key] = renameKey(cur[key])
}
}
return cur
}
function isObject(obj) {
return obj && (typeof obj === "object");
}
function isArray(obj) {
return isObject(obj) && (obj instanceof Array);
}
Looks little tedius but it does work! Now, all we need to do is have x$t instead of $t in the HTML, and boom!
working plunker
email works because nested property address doesn't contain any $ char.
Unfortunately, I don't think there is a way to bypass this behavior, however you can make your own filter and use it in ng-repeat.
This is simple example that should work for you:
JS
app.filter('customFilter', function() {
return function(items, keyword) {
if (!keyword || keyword.length === 0) return items;
return items.filter(function(item){
var phrase = keyword.$.toLowerCase();
return item.gd$name.gd$fullName.$t.toLowerCase().includes(phrase) ||
item.gd$name.gd$familyName.$t.toLowerCase().includes(phrase) ||
item.gd$name.gd$givenName.$t.toLowerCase().includes(phrase) ||
item.gd$email[0].address.toLowerCase().includes(phrase) ||
item.gd$phoneNumber[0].$t.toLowerCase().includes(phrase) ||
(!!item.gd$organization[0].gd$orgTitle && item.gd$organization[0].gd$orgTitle.$t.toLowerCase().includes(phrase)) ||
(!!item.gd$organization[0].gd$orgName && item.gd$organization[0].gd$orgName.$t.toLowerCase().includes(phrase));
});
}
});
HTML
<tr ng-repeat="x in obj | customFilter:searchText">
Of course, you will have to add more checks for possible null values. I've just wanted to make it work on the data you've provided.
Hope, you'll find it useful.
Here's plunk
I can't comment because my reputation is less than 50 but as far as i can tell it's any property that has a $ in it's name is not used in the filter.. I tried changing the property names and this fixed the issue. Realise you may or may not have control over this.
Suppose your obj is as below:
$scope.obj=[{firstName:'Jeet',lastName:'kumar'},{firstName:'test1',lastName:'dev'},{firstName:'test2',lastName:'other'}];
Search input box
<input type="text" name="search" ng-model="searchText" />
Datatable filter by index 'firstName'
<tr ng-repeat="x in obj | filter:{firstName:searchText}">
<td>{{x.firstName}}</td>
<td>{{x.lastName}}</td>
</tr>
Datatable filter over all index
<tr ng-repeat="x in obj | filter:searchText">
<td>{{x.firstName}}</td>
<td>{{x.lastName}}</td>
</tr>
Desc:
filter:{name:searchText}
filter on the basis of 'firstName' index from your $socpe.obj array

React JSX form field map fails if accessing certain properties

I'm attempting to build a form from an array of form fields where each form field looks like this:
{
"name": "state",
"resource": "customer",
"type": "TextBox",
"assetId": "State",
"label": {
"text": "State",
"assetId": "Label"
}
}
However, when I attempt to map it using JSX, the fields don't get successfully displayed if I access certain properties of the object. Take the following code, which functions correctly:
formfields.map(function (formfield, i) {
var returnfield = <div key={i}>{formfield.name}</div>;
switch (formfield.type) {
case "TextBox":
console.log(formfield.label);
returnfield = (
<div key={i}>
<label htmlFor="theinput">{formfield.name}</label>
<input id="theinput" type="text" value={formfield.name} />
</div>
);
break;
}
return returnfield;
});
And compare it with the code that fails:
formfields.map(function (formfield, i) {
var returnfield = <div key={i}>{formfield.name}</div>;
switch (formfield.type) {
case "TextBox":
console.log(formfield.label.text);
returnfield = (
<div key={i}>
<label htmlFor="theinput">{formfield.name}</label>
<input id="theinput" type="text" value={formfield.name} />
</div>
);
break;
}
return returnfield;
});
The astute observer will notice that the only difference between the two is that, in the second, we are logging formfield.label.text instead of formfield.label
I'm totally stumped why simply logging an object's grandchild attribute should cause the form to appear empty (i.e., with no fields). Perhaps I'm running into reserved names or something? Any ideas appreciated.
why didn't I see a javascript error in my developer console? Is there some weird thing where .map() doesn't allow errors to be raised?
After recognizing that checking for null is needed in your project well I suggest you use some concepts of javascript functional programming to compose a function that checks for falsely values before applying them in your logic.
You can use Maybe functor that returns a Maybe(null) which stops immediately. Before returning a null value to your logic and cause a boom!
You can also use Either, this is cool because it's just like maybe but you can also gve some logic to run if the value is falsely.
I have two examples for these suggestions (Copied from jsbin)
//Key container == Something map can iterate over like an object or an array.
//And am talking about the lodash / ramda.js curried map that can iterate over object not the js native one.
//Using Maybe
//Url http://jsbin.com/yumog/edit?js,console
var safeGet = _.curry(function(x,o){
return Maybe(o[x]);
//This will return Maybe(null)
//if it's some property in a container is not found
//which you can check before breaking something
});
var user = {id: 2, name: "Albert"}
var ex3 = compose(map(_.head), safeGet('name'));
assertDeepEqual(Maybe('A'), ex3(user))
console.log("exercise 3...ok!")
//Using Either.io
//url http://output.jsbin.com/bexuc/
// Write a function that uses checkActive()
//and showWelcome() to grant access or return the error
var showWelcome = compose(_.add( "Welcome "), _.get('name'))
//Here either returns a function you give it on the right if it's truthy
//and left if it's falsey (or falsy i don't know english .. )
// So you get to do something if the property in your container is not present.
var checkActive = function(user) {
return user.active ? Right(user) : Left('Your account is not active')
}
var ex1 = compose(map(showWelcome), checkActive);
assertDeepEqual(Left('Your account is not active'), ex1({active: false, name: 'Gary'}))
assertDeepEqual(Right('Welcome Theresa'), ex1({active: true, name: 'Theresa'}))
Links to the libraries.
Maybe: https://github.com/chrissrogers/maybe
Either: https://github.com/fantasyland/fantasy-eithers
You might also need to check on lodash / ramda to have a full idea on these functional concepts.

"ng-group" feature exists on ng-repeat?

Using non-3rd party plugins:
I have an array like this:
[
{groupName:'General', label:'Automatic Updates', type:'select', values:{0:'On', 1:'Off'}},
{groupName:'General', label:'Restore Defaults', type:'button', values:['Restore']},
{groupName:'General', label:'Export & Import', type:'button', values:['Export', 'Import']},
{groupName:'Timing', label:'Double Click Speed', type:'text'},
{groupName:'Timing', label:'Hold Duration', type:'text'}
]
I want to ng-repeat over this but create groups.
The final result I'm hoping will look like this:
So basically that is a ng-repeat on the groupName to make two div containers, then it ng-repeats for each item within to add the rows.
Is this possible without having to change my array into an object like this:
[
'General': [...],
'Timing': [...]
]
If you want to do something like this, one solution is to split up the repeat into 2 separate repeats. the easiest is to do that by creating a small helper filet that filters out the unique properties. a filter like this would do:
function uniqueFilter() {
return function(arr,property) {
if (Object.prototype.toString.call( arr ) !== '[object Array]') {
return arr;
}
if (typeof property !=='string') {
throw new Error('need a property to check for')
}
return Object.keys(arr.reduce(isUn,{}));
function isUn(obj,item) {
obj[item[property]] = true;
return obj;
}
}
}
That filter will return an array that consist of the unique values of the property you want to group by.
Once you have this you can nest a couple of ngRepeats like this:
<div ng-repeat="group in vm.data| uniqueFilter:'groupName'">
{{group}}
<ul>
<li ng-repeat="item in vm.data| filter:{groupName:group}">{{item.label}}</li>
</ul>
</div>
And you should be set.
There is no need to pull in a 3rth party for this.
see it in action in this plunk.

Resources