Angular 2: How to link form elements across a dynamically created components? - angular2-forms

I have a set of form fields that are in a dynamically created component. The parent Component owns the form tag. However, none of the form fields are being added to the Form. I'm using the ComponentFactoryResolver to create the component:
#Component({
selector: 'fieldset-container',
templateUrl: './fieldset-container.component.html',
styleUrls: ['./fieldset-container.component.scss'],
entryComponents: ALL_FIELD_SETS,
})
export class FieldsetContainerComponent<C> {
fieldsetComponent : ComponentRef<any> = null;
#Input() formGroup : FormGroup;
#ViewChild('fieldSetContainer', {read: ViewContainerRef})
fieldsetContainer : ViewContainerRef;
#Output() onComponentCreation = new EventEmitter<ComponentRef<any>>();
constructor(private resolver : ComponentFactoryResolver) {
}
#Input() set fieldset( fieldset : {component : any, resolve : any }) {
if( !fieldset ) return; // sorry not right
// Inputs need to be in the following format to be resolved properly
let inputProviders = Object.keys(fieldset.resolve).map((resolveName) => {return {provide: resolveName, useValue: fieldset.resolve[resolveName]};});
let resolvedInputs = ReflectiveInjector.resolve(inputProviders);
// We create an injector out of the data we want to pass down and this components injector
let injector = ReflectiveInjector.fromResolvedProviders(resolvedInputs, this.fieldsetContainer.parentInjector);
// We create a factory out of the component we want to create
let factory = this.resolver.resolveComponentFactory(findComponentForFieldset(fieldset.component));
// We create the component using the factory and the injector
let component : ComponentRef<any> = factory.create(injector);
// We insert the component into the dom container
this.fieldsetContainer.insert(component.hostView);
// Destroy the previously created component
if (this.fieldsetComponent) {
this.fieldsetComponent.destroy();
}
this.fieldsetComponent = component;
this.onComponentCreation.emit( this.fieldsetComponent );
}
}
The template:
<div #fieldSetContainer [formGroup]="formGroup"></div>
The usage of the dynamic component:
<form class="form" #omaForm="ngForm">
<div *ngFor="let fieldset of page?.fieldsets">
<fieldset-container [fieldset]="{ component: fieldset, resolve: {} }" (onComponentCreation)="onComponentCreation($event)" [formGroup]="omaForm.form"></fieldset-container>
</div>
</form>
I suspect it has something to do with the injector not being hooked up correctly, but from what I can tell it is chained to the parent. I've set a breakpoint in NgModel and it is passed a null for parent which is the problem. I traced that back up into something that looks compiled and it was just hard coding a null. So I'm not sure how that was created with hard coded nulls in there.
Any ideas on how to fix this?

Ok it turns out it has nothing to do with the dynamic nature of this component. I removed it and defined all of my components inline and it still had the problem. The issue was that having form controls inside a Component that were nested within a form tag is just not supported by Angular out of the box. Once you nest a form control in a component it can't see the NgForm anymore which is crazy.
After reading solutions on the web and seeing that no one had a good solution I designed 2 of my own directives that registered the Form into the DI container up at the NgForm, then using DI hierarchy I could inject that into another Directive that would perform the registration below.
Parent Component Template:
<form nested>
<my-component .../>
</form>
Child Component Template:
<div>
<input name="street" [(ngModel)]="address.street" required nest/>
<input name="city" [(ngModel)]="address.city" required nest/>
<input name="state" [(ngModel)]="address.state" required nest/>
<input name="zip" [(ngModel)]="address.zip" required nest/>
</div>
Once I had this in place then I could bring back my dynamic component and it worked perfectly. It was just really hard to get there.
It's really elegant and simple and doesn't require me to pass the form instance down through the layers like so many suggestions on the web show. And the work to register a form control whether it's 1 layer or 999 layers removed is the same.
IMHO NgForm and NgModel should just do this out of the box for us, but they don't which leads to complicated architecture design to accomplish moderately advanced forms.

Related

AngularDart Passing Toggle Event from one component to another Component

From here :
https://github.com/dart-lang/angular_components_example/blob/master/example/app_layout_example/lib/app_layout_example.html
I want to split this template in two templates:
one for sidebar <material-drawer>, named for example sidebar_component.{dart,html}
one other for <div class="material-content">, named for example app_component.{dart,html}
Question:
How to reach <material-drawer> from sidebar_component, with <material-button icon class="material-drawer-button" (trigger)="drawer.toggle()"> into app_component?
Components are encapsulated on purpose. So there isn't a super easy way to reach into the encapsulation of one component from the other.
What you can do is create a passthrough from one component to the other.
<side-bar #sidebar></side-bar>
<app-component (openSideBar)="sidebar.toggle()"></app-component>
sidebar_component
#Component()
class SidebarComponent {
#ViewChild(MaterialPersistentDrawerDirective)
MaterialPersistentDrawerDirective drawer;
void toggle() => drawer.toggle();
}
app_component.dart
#Component()
class AppComponent {
final _openSideBar = StreamController<void>();
#Output()
Stream<void> openSideBar => _openSideBar.stream;
// This is getting called by the trigger of the button click
void onButtonClick() => _openSideBar.add();
}
I would say that for me passing all of these events feels like a bit of a smell. The components themselves are breaking encapsulation and so I wouldn't architect the app exactly like that.
I would probably have the contents of the drawer be a component, and perhaps the header and body depending on how complex they got. To have something more like this:
<material-drawer #drawer>
<side-bar *deferredContent></side-bar>
</material-drawer>
<div class="material-content">
<app-header class="material-header shadow" (triggerDrawer)="drawer.toggle()">
</app-header>
<router-outlet></router-outlet>
</div>
I find it better to keep the app-layout logic in the same components if possible and encapsulate the pieces of that. You could also pass the drawer as an input, but then you are making those highly coupled which I tend to try not to do.

Angular2 code on when some change in Textbox how its Reflect back to component

Here i have requirement like if i type type anything in Textbox it should be Reflect back to Component
<label> Search FRom Table:</label> <input (change)="someval()">
Someval(){
//Here i need that TextBox value
}
You can achieve this in Two way data binding method and so on.
Html Looks like following
<label> Search From Table:</label>
<input name="searchTxt" [(ngModel)]="searchTxt" (input)="someval()">
{{searchTxt}} <!-- here iam printed html file also -->
Typescript File:
//Add one variable
public searchTxt:any;
someval(){
console.log("Here iam printed variable",this.searchTxt);
}
Make sure you have to import Forms Module in app module file
other wise it show error like this
app.module.ts
import { FormsModule } from '#angular/forms'
imports: [
FormsModule,
],
For more details about related here, please visit:
https://www.code-sample.com/2017/05/angular2-input-textbox-change-events.html
Angular 2 change event on every keypress

New to React: Why is one array treated differently than the other?

I'm working on a React app that is fed data from a Rails api. I'm currently working on a form that includes a nested association (i.e. in the model_a has many model_b's and you can create them in the same form).
The problem I'm having is that Rails expects nested association with a certain naming convention and the same field that controls how the parameter is named when its sent to rails also controls how React finds the right data when the Rails API responds.
This becomes problematic on the edit page because I want to show the models_a's (Retailers) already existing model_b's (SpendingThresholds in this case) and when I change the 'name' field to suit the rails side, React doesn't know where to look for that data anymore. When I try to pass the data directly it comes in as a different type of array and certain functions fail.
I think its easier to show than tell here so
initially I had this
<FieldArray
name="spending_thresholds"
component={renderSpendingThresholds}
/>
and data was coming through like
Object {_isFieldArray: true, forEach: function, get: function, getAll: function, insert: function…
to my React app from the Rails API, which worked, however that 'name' isn't to Rails liking (Rails wants it to be called 'spending_thresholds_attributes' for accepts_nested_attributes to work) so I changed it to
<FieldArray
name="spending_thresholds_attributes"
fields={this.props.retailer.spending_thresholds}
component={renderSpendingThresholds}
/>
and data start coming through to the renderSpendingThresholds component in this format
[Object]
0:Object
length:1
__proto__:Array(0)
which React doesn't like for some reason.
Anyone know how to fix this/why those two objects, which hold the same information from the Rails side anyway, are being treated differently?
EDITS
renderSpendingThresholds component
The fields attribute in the renderSpendingThresholds component is the object that's coming through differently depending on how I input it
const renderSpendingThresholds = ({ fields }) => (
<ul className="spending-thresholds">
<li>
<Button size="sm" color="secondary" onClick={(e) => {
fields.push({});
e.preventDefault();
}
}>
Add Spending Threshold
</Button>
</li>
{fields.map((spending_threshold, index) => (
<li key={index}>
<h4>Spending Threshold #{index + 1}</h4>
<Button
size="sm"
color="danger"
title="Remove Spending Threshold"
onClick={() => fields.remove(index)}
>
Remove
</Button>
<Field
name={`${spending_threshold}.spend_amount`}
type="number"
component={renderField}
label="Spend Amount"
placeholder="0"
/>
<Field
name={`${spending_threshold}.bonus_credits`}
type="number"
component={renderField}
label="Bonus Credits"
placeholder="0"
/>
</li>
))}
</ul>
);
It looks like you are passing fields through props and then destructuring the fields out of the props in the callback of the renderSpendingThresholds and discarding the rest. According to the docs, a specific redux-form object is passed through to the render callback. You're essentially overwriting this. Try changing {field} to something like member or spending_threshold. Then you can use the specific map function to iterate over the spending_threshold items. Your field prop should still be available under member.fields or something similar.
For the code that you currently show, who exactly handles the submission?
you use the original flow of form submit?
if so, so please handle that by yourself.
** this line of code, looks weird:
onClick={() => fields.remove(index)}
as you interact directly with the state values...
you need to update the state through
this.setState({fields: FIELDS_WITHOUT_ITEM})
and now when you need to handle your own submission, you don't really care of the input names. Because you are using the state as input.
ie:
class FormSpending extends Component {
handleSubmit() {
var fieldsData = this.state.fields.map(field => {
return {
whateverkey: field.dontcare,
otherKey: field.anotherDontCare
};
});
var formData = {
fields: fieldsData
};
ajaxLibrary.post(URL_HERE, formData).....
}
render() {
return (
...
<form onSubmit={()=>this.handleSubmit()}>
...
</form>
...
);
}
}

Using Razor inside VueJs Component

To avoid repeat re-writing the same code multiple times,I'm using VueJs component feature to make a component that includes the Select dropdown list.
The code goes like this
Vue.component('select-component', {
template: `
<label>elType</label>
<div class="col-md-colwidth">
<select>
<option value=""></option>
#foreach (elType s in ViewBag.elTypes)
{
<option value="#s[elType+"ID"]">#s["Designation"+elType]</option>
}
</select>
<input type="hidden" v-model="elTarget">
</div>
`,
props: {
elType: {
type: String,
default: 'User'
},
elTarget: {
type: String,
default: 'user'
},
colwidth: {
type: int,
default: '3'},
}
})
As you can see, I'm requiring some data list I brought from the ViewBag
but all i get is that the Razor is always ignoring that it is inside a Vue Component and giving "The type or namespace name 'elType' could not be found ".
P.S:
1) the input Hidden is used in the original code to manipulate the bs jQuery select2
2)Don't mind the elTarget and elType :p it's actually same thing except I'm lazy to camelCase the word :p
3)I tried to wrap the inside #{ } but still toggle the same error
You can't use Razor 'inside' a Vue component because Razor generates the page server-side before Vue gets to do its stuff in the browser. What you have there is a Vue component inside a Razor page. elType is defined as a Vue prop, so it likely isn't in your view bag?
In any case, please don't do this! Use Razor or Vue. If you choose Vue, your vue components are static .js or .vue files, your data arrives via AJAX calls, and you loop through elTypes with v-for, in the browser. Any other way will lead to madness :-)
You could send your razor with props to the component if necessary:
View file
<component-name :prop1="#Model.somethingOtherThanString" prop2="#Model.aString"></component-name>
Vue file
props: {
prop1: Boolean,
prop2: String
}

How to use a knockout component view with parent layout?

I am working on replacement ASP.NET MVC+Knockout with just Knockout, I want to remove ASP.NET and get just static js + html.
My ASP.NET views consist of Partial views (I call them widgets in my project), this Partial views easily replaced with Knockout components.. but I have a problem: ASP.NET Partial views have a Layout (some html decoration for every widget), how can I achieve similar for Knockout component view?
Simplified example. Old asp.net scheme:
View.cshtml:
<div>
#Html.Partial("SomeWidget")
</div>
SomeWidget.cshtml:
#{
Layout = "~/Views/Shared/_WidgetLayout.cshtml"; <!-- parent layout for widget -->
}
<span>This is some widget</span>
_WidgetLayout.cshtml:
<div>
<span>This is decorator for every widget</span>
#RenderBody() <!-- render widget view here (SomeWidget.cshtml in this example) -->
</div>
New knockout-only scheme:
View.html:
<div>
<some-widget></some-widget>
</div>
View.js:
ko.components.register('some-widget', { require: 'app/SomeWidget' });
SomeWidget.html:
<span>This is some widget</span>
SomeWidget.js:
var view = require('text!/views/SomeWidget.html');
return { template: view };
How to replace _WidgetLayout.cshtml in Knockout?
There are several ways you could possibly do this. The simplest way I can think of is to have a template component, and you nest the widget inside this. KO Components support nesting.
You can define a template component thus:
ko.components.register("widget-template", {
viewModel: function(params) {
var self=this;
self.WidgetName = params.widget;
},
template: "<div class='b'><span>This is decorator for every widget</i>
<div data-bind='component: { name: WidgetName }'></div></div>"
});
To use this, you put the template-widget into your HTML, and pass the name of the widget as a parameter:
<widget-template params="widget: 'widget1'"></widget-template>
Then you define a widget as another component:
ko.components.register("widget1", {
template: "<h3>Widget One</h3>"});
So now you have a re-usable template that can wrap any component. You can see more about this binding in the Knockout documentation.
See the full JS fiddle here: http://jsfiddle.net/Quango/a8h2bwtc/
Note that you can also make the name an observable rather than a static value, as seen here:
http://jsfiddle.net/Quango/tnphvvgd/

Resources