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?

+3


source to share


3 answers


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.

+1


source


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.

0


source


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.

0


source







All Articles