Angular 2: Multiple Form - javascript

I have a component which contains child components. Each child contains a form, built using FormBuilder.
I have included JADE template inside the components just for clarity.
Further I load the child components using the component router.
#Component({
selector: 'parent',
template: `
.detail-page
.detail-header
.ui.secondary.menu.inverted.orange
.item Inquiry Details
.right.menu
a.item((click)='add($event)')
i.plus.icon
a.item((click)='save($event)')
i.save.icon
.detail-content
router-outlet
.detail-footer
a.item([routerLink]="['Child1']")
a.item([routerLink]="['Child2']")
`
})
#RouteConfig([
{ path: '/child1', name: 'Child1', component: Child1, useAsDefault: true },
{ path: '/child2', name: 'Child2', component: Child2 }
])
export class Parent {
save(event) {
event.preventDefault();
// validate all child forms
???????
}
}
#Component({
template: `
form([ngFormModel]='childForm1')
input(type='text', [control]="field1")
input(type='text', [control]="field2")
`
})
export class Child1 {
constructor(
private _formBuilder: FormBuilder
) {
this.childForm1 = this._formBuilder.group({
field1: this.field1,
field2: this.field2
});
}
}
#Component({
template: `
form([ngFormModel]='childForm2')
input(type='text', [control]="field1")
input(type='text', [control]="field2")
`
})
export class Child2 {
constructor(
private _formBuilder: FormBuilder
) {
this.childForm2 = this._formBuilder.group({
field1: this.field1,
field2: this.field2
});
}
}
I need to validate the all the child forms when the save button is pressed.
As I understand only one child component is active/initialized at the end of the routing. Thus I cannot loop over the components to validate the forms.
What is the best approach to design such a component where it is not user friendly to have a long vertical form but have it broken down into manageable child form components?
Is there a way to re-use the components created?
I was thinking I can use the dynamic component loader but still I only get access to the current loaded component.
Your suggestions/help is much appreciated.
Thank You

You can add all forms at once and just show a selection of them at once using
[hidden]="someExpression"
You can still wrap the parts in components to keep the size of the template of the parent small, but validation only runs for on elements that actually exist in the DOM. (If you wrap parts of the form into components, then the forms in these components would be validated individually and you have to collect the results to get the status for the whole.)

Related

Sharing data from main page to child page in angular 8.2.4

i am new to angular and trying to share data between pages on load of main page , i am using angular dynamic forms to create dynamic pages.
my main page consist of child components pages that load in main page but when the data from my main page is passed to the child page using providers on page load the data reaches the child but doesnt store it in variable of child page
main page:
import { Component, Input, OnInit } from '#angular/core';
import { AppSelect } from '../component/multiselect.component';
#Component({
providers: [AppSelect], //child
selector: 'app-hello',
template: `<app-select></app-select>`,
styles: [`h1 { font-family: Lato; }`],
})
export class HelloComponent implements OnInit {
array: any[] = ['1', '2', '3', '4', '5', '6'];
constructor(private module: AppSelect //child) {}
ngOnInit() {
this.module.valuefrommainrpage(this.array); //value from here is sent to multiselect.component page
}
}
multiselect.component child page :
import { Component, VERSION } from '#angular/core';
#Component({
selector: 'app-select',
template: `
<select multiple="multiple" name="" id="" style="width:300px;height:300px;">
<option *ngFor="let items of module" [value]="items">{{items}}</option>
</select>
<br>
<button (click)="setvalue()">CLICK</button>
<button
(click)="console()">consolevalues</button>`,
styles: [`h1 { font-family: Lato; }`],
})
export class AppSelect {
module: string[] = [];
setvalue() {
this.module = ['one', 'two', 'three', 'four', 'five'];
}
valuefrommainrpage(data) {
//data is recievd from main page on ngOnInit of mainpage
console.log(data);
this.module = data; //data should be set here but doesnt
}
console() {
console.log(this.module);
}
}
my stackblitz code : Sharing data from main to child component
i dont understand how this is happening as i am receiving data from main page but cannot store it in variable
As Angular docs says:
A common pattern in Angular is sharing data between a parent component
and one or more child components. Implement this pattern with the
#Input() and #Output() decorators.
Sending data to a child component
It is necessary to create a variable with #Input() decorator:
#Input() item = ''; // decorate the property with #Input()
and in parent component set value to this variable:
<app-detail-component [item]="anItem"></app-detail-component>

how to pass form value to another component in angular

I want to show taxDetailsId in my child component Html page.
But when click submit button.
After click submit button then shows taxDetailsId in my child component Html page.
Parent Component
export class OnlinePaymentComponent implements OnInit {
HttpClient: any;
paymentForm: FormGroup = this.formBuilder.group({
taxDetailsId: ['', [Validators.required]]
});
constructor(
private formBuilder: FormBuilder,
private router: Router,
) {}
ngOnInit() {}
submitForm(): void {
if (!this.paymentForm.valid) {
this.router.navigate(['/home/online-payment/error']);
return;
}
}
}
Parent.Component.html
<form [formGroup]="paymentForm" (ngSubmit)="submitForm()">
<label>Tax Details Id</label>
<input type="text" formControlName="taxDetailsId" placeholder="Tax Details Id" />
<button>Pay Bill</button>
<form>
Child Component
export class OnlinePaymentErrorComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}
Child.Component.html
<div>
<button [routerLink]="['/home/online-payment']" >Back Home</button>
</div>
you can try this pattern this.router.navigate(['/heroes', { id: heroId }]);
https://angular.io/guide/router
you can use angular #Input() decorator for it.
Child Component
import { Component, Input } from '#angular/core';
export class ChileComponent {
#Input() public taxDetailsId: number;
}
Child Component HTML
enter code here
<div>
{{ taxDetailsId }}
<button [routerLink]="['/home/online-payment']" >Back Home</button>
</div>
Parent Component HTML
<app-child-component [taxDetailsId]="taxDetailsId"> </app-child-component>
https://angular.io/guide/inputs-outputs
You can pass components around using Angular's InjectionToken.
First you start off by creating the token:
export const ONLINE_PAYMENT_REF = new InjectionToken<OnlinePaymentComponent>('OnlinePaymentComponent');
Next you add the token to one of the root components as a provider, in this case it is the OnlinePaymentComponent. This way everything that is a child of this component, and everything that is a child of those components, and so on; will have a reference to the main parent that we create here:
#Component({
selector: 'online-payment',
template: `
<online-payment-error></online-payment-error>
`,
providers: [
{
provide: ONLINE_PAYMENT_REF,
// Forwards the instance of OnlinePaymentComponent when injected into
// the child components constructor.
useExisting: forwardRef(() => OnlinePaymentComponent)
}
]
})
export class OnlinePaymentComponent {
message = 'I am the Online Payment Component';
}
Now that we have the main component setup, we can access it through the constructor of anything that is a child of OnlinePaymentComponent (no matter how deep it is).
#Component({
selector: 'online-payment-error',
template: `
<h2>Child</h2>
<strong>Parent Message:</strong> {{parentMessage}}
`
})
export class OnlinePaymentErrorComponent implements OnInit {
parentMessage = '';
constructor(
#Inject(ONLINE_PAYMENT_REF) private parent: OnlinePaymentComponent
) {}
ngOnInit() {
this.parentMessage = this.parent.message;
}
}
When all is said and done, you will see the following:
The pros of this method are that you don't have to bind values to the elements in the template, and if those components have components that need to reference the parent you wouldn't have to bind to those either. Since components look up the hierarchy till they find the first instance of the provider that we are looking for they will find the one in OnlinePaymentComponent.
This becomes very helpful when components get deeper and deeper into the parent component (say 5 levels deep), that means every time you would have to pass a reference to the template element 5 times, and if it changes or gets deeper you would have to update all the templates.
With this method we no longer need to update templates to pass data from one component to another component, we just request it in our constructor as seen in OnlinePaymentErrorComponent.
There are two simple ways:
Using query params (without routerLink).
Using Observables.
Using query params, you can use the router.navigate and pass the params you need (Id) along with the route.
eg: this.route.navigate(['yourroute/route', { tId: variableWithId }])
Using Observable, when you click on the button, use the same router navigate without params and pass the required data to an observable. On successful routing to the next page, get the resolved data from the observable.

Angular pass input variable to another component on button click

I come from a Python background, but I started trying to learn Angular and I'm really having trouble. Working between components is confusing to me and I can't figure it out. I made a good example that I think if someone helped me with it would go along way towards understanding Angular.
I just have two components: a "header" component and an app component. In the header component, I ask for the user's name and they click a button, and then it should show "Hello {{name}}" in the next component. I cannot get it to work to say the least and it's really frustrating. The Header part seems to work okay, but it's just not communicating with the other component at all. Neither the button part or the "name" part are working so I am clearly misunderstanding something I need to do when it comes to listening from the parent component.
Here is my Header HTML:
Name: <input type="text" id="userInput" value="Joe">
<button (click)=showName()>Show More</button>
Here is my Header TS:
import { Component, OnInit, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
bodyDiv = false;
inputName = '';
#Output() buttonClicked = new EventEmitter();
constructor() { }
ngOnInit() {
}
showName() {
console.log('showName clicked.');
this.bodyDiv = true;
this.inputName = document.getElementById('userInput').value;
console.log(this.inputName);
console.log(this.bodyDiv);
this.buttonClicked.emit(this.bodyDiv);
this.buttonClicked.emit(this.inputName);
}
}
Here is the main Component's HTML:
<app-header (buttonClicked)='showNextComponent($event)'></app-header>
<p *ngIf="![hiddenDiv]" [inputName]="name">Hello {{ name }} </p>
Here is the main component's TS:
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
hiddenComponent = true;
title = 'show-button';
showNextComponent() {
console.log('Button clicked.');
this.hiddenComponent = false;
console.log(this.hiddenComponent);
}
}
So who can show me what I'm doing wrong and help figure out Angular a little better? :) Thank you!
replace showName function with below code :
showName() {
console.log('showName clicked.');
this.bodyDiv = true;
this.inputName = document.getElementById('userInput').value;
console.log(this.inputName);
console.log(this.bodyDiv);
this.buttonClicked.emit(this.inputName);
}
replace below code in your main component.
name:string
showNextComponent(value:string) {
this.name = value;
}
replace below code in your html :
<app-header (buttonClicked)='showNextComponent($event)'></app-header>
<p *ngIf="name">Hello {{ name }} </p>
Please let me if you have any question and I would suggest try to use ngmodel or something else instead of directly communicating with the DOM.
Here is a slightly modified and working sample: https://stackblitz.com/edit/angular-jhhctr
The event emitter in the header component emits the name (string) which is the $event in showNextComponent($event). You have to capture this in the main component and assign it to a local variable to be able to use it in the main component's template as {{name}}
[inputName]="name" is incorrect. You can pass values like that to angular components not to actual HTML DOM elements.
There are couple of ways to communicate from one component to another in angular - Using #Input()in your child component will expects an input from parent component and #Output() from your child component will emit an event from the child component
So in your case if you want to pass a value from parent to child you need to use input property or decorator on your child property - I will provide you the code but just go through proper guidance from the link provided this will make you to create better angular applications https://angular.io/guide/component-interaction
First you need to swap your components your header component should be your parent and the child component will be your main component - if you want to work in the same way just move your codes vice versa
Header html
Name: <input type="text" id="userInput" name='userInput' [(ngModel)]='inputName' value="Joe">
<button (click)=showName()>Show More</button>
<div [hidden]='bodyDiv'>
<app-header [bindName]='inputName'></app-header>
</div>
Header Component
import { Component, OnInit, Output, EventEmitter } from '#angular/core';
#Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.css']
})
export class HeaderComponent implements OnInit {
bodyDiv = true;
inputName = '';
constructor() { }
ngOnInit() {
}
showName() {
bodyDiv = false;
}
}
Main Component Html
<p>Hello {{ bindName }} </p>
Main component ts
import { Component, Input } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
#Input()
bindName: string;
}
In your header component the inputName property will be binded using two way data binding where i used [(ngModel)]='inputName' so whatever you enter in the input text it will be updated in your inputName property
Now we need to do only one thing just to show your child component with any event - so when the button is clicked the div with [hidden] property will be false and it will be displayed and as we pass the inputName to the child Component it will be updated
And finally the child component will be displayed and the input written in the text will be updated in the child component - when the child component html displays the bindName will be updated and there will be result you expected
That's all I think this should work well - Try this and let me know - Thanks Happy coding !!
Don't forget to look into the link above where you can see many types of component interactions

Display <my-component> by [innerHTML]

I'm trying to have my custom component tags in a string array and bind them by ngfor to the innerhtml property after sanitizing them by calling bypassSecurityTrustHtml... unfortunately the output is always empty, but there is also no sanitize error...
What am i doing wrong?
// adminpanel.component.ts
#Component({
selector: 'admin-panel',
templateUrl: './adminpanel.component.html'
})
export class AdminPanelComponent {
static GetRoutes(): Route[] {
return [
{ path: '', redirectTo: 'news', pathMatch: 'full' },
// 0
{ path: 'news', component: AdminNewsViewComponent },
// 1
{ path: 'users', component: AdminUsersViewComponent },
// 2
{ path: 'roles', component: AdminRolesViewComponent },
// 3
{
path: 'culturesettings',
redirectTo: 'culturesettings/wordvariables'
},
{
path: 'culturesettings',
component: AdminCultureSettingsViewComponent,
pathMatch: 'prefix',
children: AdminCultureSettingsViewComponent.GetRoutes()
},
// 4
{
path: 'account',
component: AdminAccountViewComponent
}
]
}
panels: AdminPanel[] = [];
routedTabs: RoutedTabs
constructor(private authService: AuthService, private routerService: RouterService, private sanitizer: DomSanitizer) {
this.routedTabs = new RoutedTabs("admin/panel", 2, authService, routerService);
var routes = AdminPanelComponent.GetRoutes().filter(x => x.component != undefined);
var comps = [
'<admin-news-view></admin-news-view>',
'<admin-users-view></admin-users-view>',
'<admin-roles-view></admin-roles-view>',
'<admin-culture-settings-view></admin-culture-settings-view>',
'<admin-account-view></admin-account-view>'
];
for (var i = 0; i < comps.length; i++) this.panels.push(new AdminPanel(i, routes[i], this.sanitizer.bypassSecurityTrustHtml(comps[i]) , this.sanitizer));
}
ngOnInit() {
this.routedTabs.MakeTabs(AdminPanelComponent.GetRoutes());
this.routedTabs.Subscribe();
this.routedTabs.Emit();
}
ngOnDestroy() {
this.routedTabs.Unsubscribe()
}
}
class AdminPanel {
index: number;
route: Route;
innerHtml: any = '';
constructor(index: number, route: Route, innerHtml: any, private sanitizer: DomSanitizer) {
this.index = index;
this.route = route;
this.innerHtml = innerHtml;
}
}
And in my adminpanel.component.html:
<mat-tab-group (selectedTabChange)="routedTabs.onTabChange($event)" [(selectedIndex)]="routedTabs.selectedTab">
<mat-tab *ngFor="let panel of panels" label="{{ routedTabs.tabs[panel.index].label }}">
<div [innerHTML]="panel.innerHtml">
</div>
</mat-tab>
</mat-tab-group>
From what I gather, this has not really been resolved in a satisfying or clean manner. I've been in the same boat and didn't find a good solution to loading components in dynamic strings either - so I've written my own with ngx-dynamic-hooks!
Some key points you might be interested in:
Finds all component selectors inside a string and autormatically loads the corresponding components in their place
Can even load components by other text pattern than their selectors, if that is what you need
Inputs and outputs can be set just like in a normal template and are automatically parsed from strings into actual variables for you
Components can be nested without restrictions and will appear in each others "ng-content"-slots as expected
You can pass live data from the parent component into the dynamically loaded components (and even use it to bind inputs/outputs in the content string)
You have meticulous control over which components are allowed to load on an outlet-to-outlet-basis and even which inputs/outputs you can give them
You can optionally configure components to lazy-load only when they are needed
The library uses Angular's built-in DOMSanitizer to be safe to use even with potentially unsafe input
The components are created with native Angular methods and behave just like any other component in your app. I hope this helps all who enounter the same problem.
See it in action in this Stackblitz.
Just using the tag as a string won't work, as angular doesn't just create component instances if an element with matching selector pops up in the dom.
You either
use the ComponentFactoryResolver
just have a type property on panel so you use ngSwitch based on type and render the corresponding in an ngSwitchCase. In that case you would have the tags in your template though

Angular 4, run several instances of a component when page is opened in iframe

I'd appreciate if someone could advise on my problem with Angular data binding in a component.
I have the app that loads a component dynamically into some modal using iframe. The user can open several instances of a single page, for example fill the form with data A in modal A and fill the same form with data B in modal B, so that two modals appear on the page at the same time. This is the same as if I opened single page in several browser tabs.
The problem is when I change some ngModel in one form, it automatically changes in another, so when I enter data into the form, all opened forms receive the same values.
My root component:
index.html
<html>
...
<body>
<kb-root>
</kb-root>
</body>
</html>
root.component.ts
#Component({
selector: "kb-root",
template: "<router-outlet></router-outlet>"
})
export class rootComponent {}
app.module.ts
const appRoutes: Routes = [
{ path: '', component: appComponent },
}
#NgModule({
imports: [ RouterModule.forRoot(appRoutes)] })
app.component.ts
#Component({
selector: "kb-app",
templateUrl: "..",
})
export class appComponent{}
app.desktop.ts
export class appDesktop{
parent: appComponent = null;
constructor(private inj: Injector) {
this.parent = this.inj.get(appComponent);
}
//fires on link click, where route is the href of the link
openApp(route: string) {
$("kb-app").append('<div id="' + target + '"><iframe src="/' + route+ '"></div>');
}
}
I'm new to Angular and would be grateful for any suggestions or references on preventing this behaviour.

Categories

Resources