I'm trying to push another formbuilder within a formarray but it gives me an error since I think there are no items in the array when initializing the code, hence there are no controls. The error is Property 'controls' does not exist on type 'AbstractControl' after the
(<FormArray>this.loanTypeForm.controls['frequency']).controls[index]
I'm using angular 2.0.0-beta.17
let settingsForm: FormArray = new FormArray([]);
(<FormArray>this.loanTypeForm.controls['frequency']).push(
this.formBuilder.group({
'name': [value, Validators.required],
'settings': settingsForm,
})
);
(<FormArray>this.loanTypeForm.controls['frequency']).controls[index].controls['settings'].push(
this.formBuilder.group({
'term': [null, Validators.required],
'eir': [null, Validators.required],
})
);
You can use ['controls'] instead of .controls, as below:
(<FormArray>this.loanTypeForm.controls['frequency']).controls[index]['controls']['settings'].push(...)
But in order to simplify and provide more readability I'd suggest you to change it all to:
const control = this.loanTypeForm.get(`frequency.${index}.settings`) as FormArray;
control.push(...);
get() is the preferred way to access form controls
this.loanTypeForm.get(`frequency.${index}.settings`)
It seems that the loanTypeForm is treated as AbstractControl... so let's assure compiler that it is FormGroup
var group = this.loanTypeForm as FormGroup;
var array = group.controls['frequency'] as FormArray;
var control = group.controls[index]; // AbstractControl again.. could be casted as needed
and in case, that control is also group or form we just have to use assert (cast) as well
var control = group.controls[index] as FormGroup
And then we can easily continue
control.controls['settings']...
Here is My solution if you are Following the Angular Course on Udemy
This code will fail with the latest Angular version.
You can fix it easily though. Outsource the “get the controls” logic into a getter of your component code (the .ts file):
get controls() { // a getter!
return (<FormArray>this.recipeForm.get(‘ingredients’)).controls;
}
In the template, you can then use:
*ngFor="let ingredientCtrl of controls; let i = index"
This adjustment is required due to the way TS works and Angular parses your templates (it doesn't understand TS there)
below code is working
<FormArray>this.loanTypeForm.controls['frequency']).controls[index]['controls']['settings'].push(...)
Related
I have a model:
export default Model.extend({
title: attr('string'),
attributes: attr('jsonb')
});
Where attributes is a custom json filed stored as jsonb in Postgres.
let say:
{
"name":"Bob",
"city":""
}
So I can easily manipulate attributes using template
<form.element .. #property="attributes.city"/> or model.set('attributes.city','city name')
Problem: hasDirtyAttributes do not changing because technically we have old object. But when I try to copy object let say
JSON.parse(JSON.stringify(this.get('attributes')) hasDirtyAttributes works as expected
So how to write some Mixin for a Model or other workaround which on the change of any attribute property will mark hasDirtyAttributes as true. I will update whole object so doesn't matter which property actually was changed.
Same problem: https://discuss.emberjs.com/t/hasdirtyattributes-do-not-work-with-nested-attributes-json-api/15592
existing solution doesn't work for me at all:
ember-dirtier
ember-data-relationship-tracker
ember-data-model-fragments (a lot of changes under the hood and broke my app)
Update:
Some not perfect idea that help better describe what I'm want to achieve:
Let say we adding observer to any object fileds:
export default Model.extend({
init: function(){
this._super();
this.set('_attributes', Object.assign({}, this.get('attributes'))); //copy original
Object.keys(this.get('attributes')).forEach((item) => {
this.addObserver('attributes.'+ item, this, this.objectObserver);
});
}
...
})
And observer:
objectObserver: function(model, filed){
let privateFiled = '_' + filed;
if (model.get(privateFiled) != model.get(filed)) { //compare with last state
model.set(privateFiled, this.get(filed));
model.set('attributes', Object.assign({}, this.get('attributes')) );
}
}
It's works, but when I change one filed in object due to copying object objectObserver faired again on every filed. So in this key changing every filed in object I mark observed filed as dirty
The further ember development will reduce using of event listener and two-way binding, actually Glimmer components supports only One-way Data Flow. So to be friendly with future versions of emberusing one-way data flow is good approach in this case to. So In my case as I use ember boostrap solution looks like
<form.element #controlType="textarea" #onChange={{action 'attributeChange'}}
where attributeChange action do all works.
New Glimmer / Octane style based on modifier and looks like:
{{!-- templates/components/child.hbs --}}
<button type="button" {{on "click" (fn #onClick 'Hello, moon!')}}>
Change value
</button>
I have read multiple answers to these kind of issues, and each answer has its own response;
In my case I am not getting any of those as my interfaces simply don't map the json like I want it to. I have tried multiple solutions, since working with Root-object and nested interfaces, but here I am, asking which is the best approach to deal with these kind of JSON objects in the front end, how to map it this particular one (a fork-Join). and I wanted to ask what are the real benefits of using the interfaces/classes/ maps besides the Intellisense? It has to do with data propagation?
The json structure in question:
{
Title: "",
Year: "",
Rated: "",
Released: "",
Runtime: "",
…}
Simple as it is. But back in my service I call it with a forkjoin:
getMovies(name: string, year?: string): Observable<any> {
let shortPlot = this.http.get(
"https://www.omdbapi.com/?t=" +
name +
"&plot=short&y=" +
year +
"&apikey=[my key]"
);
let fullPlot = this.http.get(
"https://www.omdbapi.com/?t=" + name + "&plot=full&apikey=[my key]"
);
return forkJoin([shortPlot, fullPlot]);
}
The subscription in the component:
getMovie() {
this.spinner = true;
this.movieService
.getMovies(this.name.value)
.subscribe((dataList: any) => {
this.movies = Array.of(dataList[0]);
this.spinner = false;
let error: any = this.movies.map(error => error.Error);
if (error[0]) {
this.notfound = error[0];
this.error = true;
} else {
this.error = false;
this.movieRate = this.movies.map(rating => rating.imdbRating.toString());
}
})),
error => console.log(error);
}
And in the HTML I render the data like this:
<div *ngFor="let m of movies">
<h5 class="mt-0">{{m.Title}}, {{m.Year}}</h5>
</div>
So as you can see I am not working with an interface and I should. Anyone can sort me out?
Thank you
EDIT: the log after subscribe:
let's break it down,
what are the real benefits of using the interfaces/classes/ maps besides the Intellisense?
Using interfaces and classes will not just give you intellisense but will also provide static type safety for your code. Why this is important, let's say you have a interface with following structure,
export interface Demo {
field: string;
}
// in some other file 1
demo.field.substring(1, 2);
// in some other file 2
demo.field.lenght;
You are using this interface in many places in your code. Now, for some reason you get to know that the property should be number not string. So here typescript will give you all the errors at compile time only.
export interface Demo {
field: number;
}
// in some other file 1
demo.field.substring(1, 2); // error
// in some other file 2
demo.field.lenght // error
Also, after typescript transpiles it will generate javascript files, now as javascript is interpreted language, your code will not be tested until the javascript run-time actually executes the problematic line, but in typescript you will get errors in compilation stage only.
You can get away with using any everywhere, but with that you will be missing the static typings.
With interfaces and classes, you also get OOP features, such as inheritance etc.
It has to do with data propagation?
Your frond-end is never aware what type of data will be received from api. So it's developers responsibility that the received data should be mapped to some interface.
Again as mentioned above, if somehow back-end changes type of some field in received json, then it will again be caught in compile time.
In case of forkJoin which combines output of two jsons you can have two different types.
Demo
export interface Demo1 {
field1: string;
}
export interface Demo2 {
field2: number;
}
// in service layer
getData(): Observable<[Demo1, Demo2]> {
const res1 = this.http.get(...);
const res2 = this.http.get(...);
return forkJoin([res1, res2]);
}
// in component
this.dataService.getData().subscribe(res => {
// you will get type safety and intellisense for res here
console.log(res[0].field1)
})
I am not working with an interface and I should.
Yes, you should use interfaces, if you are not using using features of typescript then whats the point using it. :)
I have a Validators.pattern for a field, and that's the only validation I have on my form.
Normal usage is working. It validates the field properly however the issue occurs when you try to copy and paste.
If you copy and paste on the field repeatedly on the field, it is like the validity of the field is being toggled( submit button is disabled if form is invalid )
Issue also occurs when I populate a the data from other source like search or auto-suggest.
buildForm(obj) {
this.form = this.fb.group({
field: [obj['field'] || '', Validators.pattern(/MY_REG_EX_HERE/g)],
id: [obj['id'] || ''],
});
}
I am not really sure about the main cause of the issue but as a workaround, I created custom validator with same REGEX. I will post it here and might help someone.
import { AbstractControl, ValidationErrors, ValidatorFn } from '#angular/forms';
export function customValidator(control: AbstractControl) {
if (control.value !== '') {
const isValid = (/MY_REG_EX_HERE/g).test(control.value)
return isValid ? null : { customValidator: true };
} else {
return null;
}
}
I had a similar problem. I studied it in detail and found out the reason. Like me, you are using the /g modifier. which has a side effect if the regex is assigned to a variable. This modifier indicates the continuation of the search in the string. To understand the "unexpected" behavior, take a look at the screenshot.
You can find out more information here
In my application their is some text which is coming from a constant file which is declared like this:
export const EmpStrings = {
data: "Welcome {{employee.name}}"
}
And In my component file there is an object called employee.
public employee = { name: 'xyz', dept: 'EE' }
Now In my HTML I want to use it like this:
<div class='e-data' [innerHTML] = "EmpStrings.data"></div>
But this didn't seems to be working.
I tried various variations:
[inner-html] = "EmpStrings.data"
[innerHTML] = {{EmpStrings.data}}
[innerHTML] = "{{EmpStrings.data}}"
But none of them seems to be working.
If you don't want to use JitCompiler then you can parse string yourself
component.ts
ngOnInit() {
this.html = EmpStrings.data.replace(/{{([^}}]+)?}}/g, ($1, $2) =>
$2.split('.').reduce((p, c) => p ? p[c] : '', this));
}
template
<div [innerHTML]="html"></div>
Plunker Example
use ${employee.name} to bind angular variable to innerHTML
"data": `Welcome ${employee.name}`
Angular doesn't interpolate strings like this, as far as I know one of the reason is security. The const doesn't have the context where your employee is in hence you see the error saying employee is not defined.
You could do this:
Change your constant:
export const EmpStrings = {data: "Welcome "};
then:
<div class='e-data'>{{EmpStrings.data}}{{employee.name}}</div>
But if for some reason you must use [innerHTML]:
In your component class, add a method:
getWelcomeMessage(){return EmpStrings.data + this.employee.name;}
in the component view:
<div class='e-data' [innerHTML] = "getWelcomeMessage()"></div>
I have a form that have a three field group that on a click of a "Add New" buttom other three field group will be added. That part is working great.
I want to add a validation so all three fields are required in order to add a new group.
For reference here is the code working: http://jsfiddle.net/5g8Xc/
var ContactsModel = function(contacts) {
var self = this;
self.contacts = ko.observableArray(ko.utils.arrayMap(contacts, function(contact) {
return { firstName: contact.firstName, fathersLast: contact.fathersLast, country: contact.country };
}));
self.addContact = function() {
self.contacts.push({
firstName: "",
fathersLast: "",
country: ""
});
};
self.removeContact = function(contact) {
self.contacts.remove(contact);
};
};
Any clue on how to implement this validation? I was trying to use jquery validation to do that but I think that is possible with KnockoutJS.
Appreciate any advice.
As stated already, the validation plugin will be the most elegant, less re-inventive solution.
Edit: After commentary implementation utilizing validation plugin
With that aside, you have a couple options.
If you are confident the contact object will always contain only required fields, a not very robust implementation would be iterate over the properties of the contact ensuring each has some value.
A little more robust, but still lacking the elegance of the plugin, implementation would be to maintain an array of required fields and use that array for validation. You can reference my example for this setup. Essentially, each required property is mapped to observables. Changes made to the value of any observable property triggers (via a subscription) a mutation call for a dummy observable that is used in a computed. This is required since a computed can't call valueHasMutated. The mutation call triggers the computed to reevaluate, thus updating the UI.