Giter Club home page Giter Club logo

Comments (4)

gund avatar gund commented on July 26, 2024 2

@ryanbonial you can always wrap those components in your own component that will proxy [(ngModel)] through the Input and Output.

from ng-dynamic-component.

gund avatar gund commented on July 26, 2024

You have to keep in mind that dynamically created components will not have inputs and outputs to be attached in the DOM and as a consequence your ngModel input wont tell Angular to bind ngModel to the corresponding value accessor - you have to take care about it manually.

So I added 1 more input ngModel and 1 output ngModelChange in order to simulate what angular will do.

Secondly if you want to have true 2 way binding you cannot directly update ngModel value because it will loose reference to the bounded component instance, instead you have to use dot construct like inputs.ngModel where inputs property will be bound to dynamic component inputs.

Please refer to updated plunkr for working example.

from ng-dynamic-component.

ryanbonial avatar ryanbonial commented on July 26, 2024

Thanks for the explanation and updated plunkr. It is a shame that ngModel and ngModelChange have to be implemented manually. I can add the additional Input() and Output() for my own components, but it will be tougher to use 3rd party components that use ngModel.

from ng-dynamic-component.

maxfahl avatar maxfahl commented on July 26, 2024

I solved it by wrapping the control value accessor components in a component of my own. The final class base class for these components look like this:

export class FormFieldsComponent implements AfterViewInit, OnDestroy {

	@ViewChildren(NgModel)
	formControllers: QueryList<NgModel>;

	@Input()
	public form: NgForm;

	private addedFormControllers: NgModel[];
	private formControllerChangeSub: Subscription;

	constructor() {
		this.addedFormControllers = [];
	}

	ngAfterViewInit(): void {
		this.setupFormControllers();
		this.formControllerChangeSub = this.formControllers.changes.subscribe(
			this.setupFormControllers.bind(this)
		);
	}

	private setupFormControllers(): void {
		this.resetFormControllers();
		if (this.formControllers && this.formControllers.length) {
			this.formControllers.forEach(control => {
				control.name = `${ control.name }_${ Date.now() }`;
				this.addedFormControllers.push(control);
				this.form.addControl(control);
			});
		}
	}

	private resetFormControllers(): void {
		if (this.addedFormControllers) {
			this.addedFormControllers.forEach(
				control => this.form.removeControl(control)
			);
			this.addedFormControllers = [];
		}
	}

	ngOnDestroy(): void {
		this.resetFormControllers();
		this.formControllerChangeSub.unsubscribe();
	}
}

Then I have another base class for my angular component that is placed between my angular component and the base class above. That class looks like this:

export class CustomFieldInputBase<T extends CustomField> extends FormFieldsComponent implements OnDestroy {

	@Input()
	customField: T;

	@Input()
	model: any;

	constructor() {
		super();
	}

	ngOnDestroy(): void {
		super.ngOnDestroy();
	}
}

The input model is an object i will do two way bindings on. The input customField only contains data about the field I'm rendering containing it's name and so on.

An example of my angular component class looks like this:

@Component({
	selector: 'custom-string-field-input',
	encapsulation: ViewEncapsulation.None,
	providers: [],
	templateUrl: './custom-string-field-input.component.html',
	styleUrls: ['./custom-string-field-input.component.sass']
})
export class CustomStringFieldInputComponent extends CustomFieldInputBase<CustomStringField> implements OnInit, OnDestroy {

	constructor(

	) {
		super();
	}

	ngOnInit(): void {

	}

	ngOnDestroy(): void {
		super.ngOnDestroy();
	}
}

And its HTML:

<input name="{{ customField.name }}"
	   placeholder="{{ customField.label }}{{ customField.required ? '*' : '' }}"
	   class="{{ customField.name }}-input"
	   [(ngModel)]="model[customField.name]"
	   [attr.empty]="(!model[customField.name] || !model[customField.name].trim()) ? '' : undefined"
	   [required]="customField.required"
	   autocomplete="off">

The base class FormFieldsComponent registers any children of NgModel to the form (inputed to the same class), after that it all should function as usual.

This may be quite overwhelming for some, but I didn't have much time to post this. Hopefully it can help someone :)

from ng-dynamic-component.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.