Angular Binding Dynamic Form Properties
I have a problem with some dynamically generated forms and passing values ββto them. I feel like someone must have solved this, or Im missing something obvious, but I can't find any mention of it.
So, for example, I have three components: parent, child and then child of this child. For names, Ill go, formComponent, questionComponent, textBoxComponent. Both of these children use changeDetection.OnPush.
So the form component passes some values ββup to the questionComponent through the inputs, and some use an asynchronous channel to subscribe to their corresponding values ββin the store.
QuestionComponent dynamically creates different components and then puts them on the page if they match (there are so many types of components, but each QuestionComponent only handles one component.
some code:
@Input() normalValue
@Input() asyncPipedValue
@ViewChild('questionRef', {read: ViewContainerRef}) public questionRef: any;
private textBoxComponent: ComponentFactory<TextBoxComponent>;
ngOnInit() {
let component =
this.questionRef.createComponent(this.checkboxComponent);
component.instance.normalValue = this.normalValue;
component.instance. asyncPipedValue = this. asyncPipedValue;
}
This works fine for all instances of normalValues, but not for asyncValues. I can confirm in the question ngOnChanges Components that the value is being updated, but that value is not passed to the textBoxComponent.
Basically I want an asynchronous pipe, but not for templates. Ive tried several solutions on different ways of passing asyncValues, Ive tried to detect when the asyncPipeValue changes and runs changeDetectionRef.markForChanges () on the textBoxComponent, but this only works when I change changeDetectionStrategy to normal, resulting in a performance win which I getting from using ngrx.
It seems like too much oversight not to have a solution, so I guess he just doesn't think of me. Any thoughts?
source to share
I am doing something like this and I have forms filled with data coming from my Ngrx store. My forms are not dynamic, so I'm not 100% sure if this will work for you too.
Define your input with just a setter, then call the patchValue () or setValue () function on the form / form control. Your root component remains the same, passing data to the next component using an async pipe.
@Input() set asyncPipedValue(data) {
if (data) {
this.textBoxComponent.patchValue(data);
}
}
patchValue () is in the AbstractControl class. If you don't have access to this question component, your TextBoxComponent can expose a similar method that can be called from your QuestionComponent, with the implementation updating the control.
One thing to keep in mind, if you are also subscribing to valueChanges in your form / control, you might want to set a second parameter so the valueChanges event does not fire immediately.
this.textBoxComponent.patchValue(data, { emitEvent: false });
or
this.textBoxComponent.setValue(...same as above);
Then in your TextBoxComponent
this.myTextBox.valueChanges
.debounceTime(a couple of seconds maybe)
.distinctUntilChanged()
.map(changes => {
this.store.dispatch(changes);
})
.subscribe();
This approach works very well and removes the need to use save and refresh buttons.
source to share
Here are my thoughts on the matter, given the limited code snippet.
First, the above example has nothing to do with ngrx
. In this case, it ngOnInit
is expected to be executed only once and at that time the this.asyncPipedValue
value undefined
. Therefore, if changeDetection
of this.checkboxComponent
is equal ChangeDetection.OnPush
, the value will not be updated. I recommend reading one great article on change detection and passing asynchronous inputs. This article also contains other equally significant change detection resources. Also, it seems that the same inputs are being passed twice through the component tree, which is not a good solution from my point of view.
Second, a different approach would be to use ngrx
and then you don't need to pass any async inputs at all. Moreover, this method is good if two components do not have a parent-child relationship in the component tree. In this case, one component dispatches an action to put data into the store, and another component subscribes to that data from the store.
export class DataDispatcherCmp {
constructor(private store: Store<ApplicationState>) {
}
onNewData(data: SomeData) {
this.store.dispatch(new SetNewDataAction(data));
}
}
export class DataConsumerCmp implements OnInit {
newData$: Observable<SomeData>;
constructor(private store: Store<ApplicationState>) {
}
ngOnInit() {
this.newData$ = this.store.select('someData');
}
}
Hope it helps or gives some hints at least.
source to share
I believe I figured out the solution (with some help from the gitter.com/angular channel).
Since values ββcome into question, the component can change and fire it for ngOnChanges, when there is an event in ngOnChanges, it needs to parse the event and bind it to the dynamic child component.
ngOnChanges(event) {
if (this.component) {
_.forEach(event, (value, key) => {
if (value && value.currentValue) {
this.component.instance[key] = value.currentValue;
}
});
}
}
That's all in the question Component, it resets the instance variables of the Components if they have changed. The biggest problem with this so far is that the child ngOnChanges is not firing, so this is not a complete solution. I will continue to dig in it.
source to share