I am new to Angular 4 and using MEAN stack to develop an application.
My parent component is admin and child is session. I am trying to set the dropdown value in the parent component based on child component value.I am output emitter event for the same in the insert session method. However I am unable to set the value of dropdown. Pls help.
admin html
<div>
<label for="FormType" class="col-sm-2 col-form-label">Select Type </label>
<select #s (change)="setNav(s.value)">
<option *ngFor="let item of dms" >{{item}}</option>
</select>
</div>
<div *ngIf="regTypeSelectedOption==='Session'">
<code for session></div>
<div *ngIf="regTypeSelectedOption==='webinar'">
<code for webinar>
</div>
<div (notify)='onNotify($event)'></div
admin.ts
onNotify(message: string):void{
this.setNav(message);
alert(JSON.stringify(message));
}
setNav(nav:any){
this.selectedNav = nav;
if(this.selectedNav == "Session"){
this.regTypeSelectedOption = "Session";}
else if(this.selectedNav == "Webinar"){
this.regTypeSelectedOption = "Webinar";
}
else if (this.selectedNav == "Select"){
this.regTypeSelectedOption = "Select";
}
}
session.ts
export class SessionComponent implements OnInit {
insertSession(sessionForm){
this.newSession.deliveryMethod = this.regTypeSelectedOption;
this.newSession.formType = "Session";
let updatedresult;
if(this.updatedid == undefined){
this.dataService.insertNewSession(this.newSession)
.subscribe(
res =>{ this.sessions = res;
console.log('response', res)},
err => this.apiError = err,
() => {
this.notify.emit('Session');
this.router.navigate(['/admin']);
}
)
I am using notify event in parent with div. If I use the notify event with selector of sessioncomponent entire session component is getting displayed
I think you want to send message from one component to another, without using the child component in parent component.
I had a look at your code and found you are navigating to '/admin', after selection.
I would rather recommend you to register a route in your app.routing.ts like below
{path:'/admin/:id',component:AdminComponent}
In the Admincomponent use the below code to fetch the data from the route.
import {Router, NavigationEnd, ActivatedRoute,Params } from "#angular/router";
In Admincomponent implement OnInit and add below code to read the route value
ngOnInit(){
this.activatedRoute.params.subscribe((params: Params) => {
this.params = this.activatedRoute.params;
this.regTypeSelectedOption= this.params.value.id != undefined ? this.params.value.id : "";
});
}
Now in the ChildComponent , replace the below code:
//this.notify.emit('Session');
this.router.navigate(['/admin/Session']);
Now when the session is inserted, it will route you back to the AdminComponent with Session Id
Related
I have two components first and second. From the second component, I am calling the first component. In the first component, I have a matslider module and I want to get that slider on/off status to my second component ts file. So I am getting that value in first, but don't know how to pass that to the second component.
first.component.html
<div>
<mat-slide-toggle class="toggles"
(change)="OnToggle($event)
[(ngModel)]="selected">Toggle</mat-slide-toggle>
</div>
first.component.ts
#Input() selected=false;
public OnToggle(event)
{
this.selected = event.selected;
}
second.component.html
<div class="container">
<app-first> </app-first>
</div>
I think you can use an output event in the first component and bind to it in the second component.
here it is example:
First Component:
#Output() selectedChange = new EventEmitter<boolean>();
public OnToggle(event) {
this.selected = event.selected;
this.selectedChange.emit(this.selected);
}
SecondComponent:
<app-first (selectedChange)="onSelectedChange($event)"></app-first>
public onSelectedChange(selected: boolean) {
console.log(selected);
}
Problem
I can not refresh a variable, and thus select dropdown in the club-list-component after I create a club in the create-club-component
Context:
I am developing an application which randomly select a person from a team, from a club. First I made 1 component which concluded all the functionality, but as that would be ugly I wanted to seperate the different components and functions.
What I tried:
I've tested the functionality and the dropdown-box refreshes after creating a club, if all code is contained in 1 component.
Code snippets
I have the following pieces of code to share (some left away for readability):
create-club.component.ts:
#Input() clubDetails = {name: ''};
createClub() {
this._clubService.createClub(this.clubDetails).subscribe((data: {}) => {
});
alert('Club Created');
}
club-list.component.ts:
public clubs = [];
ngOnInit() {
this.refreshClublist();
}
refreshClublist() {
this._clubService.getClubs().subscribe(data => this.clubs = data);
}
}
club-list.component.html
<div>
<div class="alert alert-primary">
Select a club from the list
</div>
<select class="form-control">
<option *ngFor="let club of clubs" [value]="club.id">
{{club.name}}
</option>
</select>
</div>
What do I try to archieve:
Once i create my club from the popup modal in create-club.component.html, I want to have the dropdown box in club-list.component.html to be refreshed
In my mind best case scenario would be:
[club-list-component] ngOnInit(refreshClublist()) {}
[create-club.component]createClub()
[club-list-component] refreshClublist() (called after createClub() in step 2)
You can achieve this using Observable BehaviorSubject
create-club.component.ts:
private clubList = new BehaviorSubject(null);
public clubList$ = this.clubList.asObservable();
createClub() {
this._clubService.createClub(this.clubDetails).subscribe((data: {}) => {
this.clubList.next(data);
});
alert('Club Created');
}
club-list-component:
ngOnInit(){
this.clubList$.subscribe(updatedList=>{
console.log(updatedList);
});
}
I display data from a template that I create in another component (team-announcements.component)... in the main team.component.ts I use the selector for team-announcements, and add the [announcement]="announcements" and ngFor="let announcement of announcements" to load the data. If the array returns no data IE there are no announcements, how can I display a placeholder like "No Announcements"?
This is where I load the announcements in team.component.html. The data is served through an API service and is retrieved in "team.component.ts", the HTML for the objects in question is below.
team.component.ts (get announcement functions):
getAnnouncements() {
this.teamsService.getTeamAnnouncements(this.team.slug)
.subscribe(announcements => this.announcements = announcements);
console.log("announcements", this.announcements);
}
team.component.html
<div class="team-announcement">
<div class="announcement-title">Message of the Day</div>
<app-team-announcements
[announcement]="announcement"
*ngFor="let announcement of announcements">
</app-team-announcements>
</div>
This is how "app-team-announcements" above is templated in a separate file, "team-announcement.component.html" and is exported, and then used in the above code...
team-announcements.component.ts
import { Component, EventEmitter, Input, Output, OnInit, OnDestroy } from '#angular/core';
import { Team, Announcement, User, UserService } from '../core';
import { Subscription } from 'rxjs';
#Component({
selector: 'app-team-announcements',
templateUrl: './team-announcement.component.html'
})
export class TeamAnnouncementComponent implements OnInit, OnDestroy {
constructor(
private userService: UserService
) {}
private subscription: Subscription;
#Input() announcement: Announcement;
#Output() deleteAnnouncement = new EventEmitter<boolean>();
canModify: boolean;
ngOnInit() {
// Load the current user's data
this.subscription = this.userService.currentUser.subscribe(
(userData: User) => {
this.canModify = (userData.username === this.announcement.author.username);
}
);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
team-announcements.component.html
<div class="announcement-text">
{{announcement.body}}
</div>
I am unsure of how or where to "If" check the array length to display a placeholder. Can anyone help?
If you want to hide it and display something else instead you can use the else property from *ngIf:
<div class="team-announcement">
<div class="announcement-title">Message of the Day</div>
<ng-container *ngIf="announcements.length != 0; else emptyArray">
<app-team-announcements
[announcement]="announcement"
*ngFor="let announcement of announcements">
</app-team-announcements>
</ng-container>
</div>
<ng-template #emptyArray>No announcements...</ng-template>
When you want an element with *ngFor to depend on a condition (*ngIf), a good alternative is to nest the element with an *ngFor in a <ng-container> with an *ngIf. A good thing about <ng-container> is that it wont actually be part of the DOM but will obey the *ngIf.
You could insert a div wich is only displayed when your array is empty:
<div class="team-announcement">
<div class="announcement-title">Message of the Day</div>
<app-team-announcements
[announcement]="announcement"
*ngFor="let announcement of announcements">
</app-team-announcements>
<div *ngIf="announcements.length===0"> No announcements </div>
</div>
Edit: Corrected the errors
How to model dynamic forms as a React Component?
For example I want to create a form shown in an image below:
How can I model this as a React component?
How can I add dynamicity to that component? For example, clicking on "+ Add" button creates another empty textbox and puts it right below the other already rendered textboxes (as shown in an image below).
Can someone help me with the code for the Form below?
In tags I see redux so I can suggest redux-form. Here you have an example of dynamic forms with redux-form.
The difference is in the fact, that beyond the state of form values, we also need to handle the state of form shape/structure.
If you render the inputs by traversing some state object, that is representing the shape of the form, than new input is just a new entry in this state object. You can easily add or remove input fields on the form by managing that state object. E.g. you can write something like this (pseudo react code):
// inputs state of math and algorithms
const state = { math: [obj1, obj2], algorithms: [obj1, obj2] } // obj ~= {id, value}
// render math inputs
const mathMarkup = state.math.map(obj => <input value={obj.value} onChange={...} />)
// add handler for math field
const addMath = () => setState(prevState => ({ math: [...prevState.math, newObj]}))
Here is the example of such form - codesandbox. It's not 100% as on your screen, but the idea should be understandable. Since there are some unclear requirements on your form, I implemented only first two sections, so you can grasp the idea. And, there are no styles :shrug:
Also, you can extract renderXyz methods to separate components, and improve state shape to meet your needs.
I can help you with a reduced way
import React , {Component} from 'react'
import { connect }from 'react-redux'
class main extends Component{
render(){
return(
<div>
<BaselineMath/>
<Algorithms />
</div>
)
}
}
const mapStateToProps = ({}) => {
return{}
}
export default connect (mapStateToProps,{})(main)
class BaselineMath extends Component{
constructor(props){
super(props);
this.state={rows:[1]}
}
_getRows{
return this.state.rows.map((res,key)=>{
return <input placeholder="etc..."/>
})
}
onClickAdd(){
let rows = this.state.rows
rows.push(1)
this.setState({
rows
})
}
render(){
return(
<div>
<Button onClick={this.onClickAdd.bind(this)}>ADD row</Button>
{this._getRows()}
</div>
)
}
}
export default (BaselineMath)
class Algorithms extends Component{
constructor(props){
super(props);
this.state={rows:[1]}
}
_getRows{
return this.state.rows.map((res,key)=>{
return <input placeholder="etc..."/>
})
}
onClickAdd(){
let rows = this.state.rows
rows.push(1)
this.setState({
rows
})
}
render(){
return(
<div>
<Button onClick={this.onClickAdd.bind(this)}>ADD row</Button>
{this._getRows()}
</div>
)
}
}
export default (Algorithms)
you can do the algorithm with anything you want
The current official docs only shows how to dynamically change components within an <ng-template> tag. https://angular.io/guide/dynamic-component-loader
What I want to achieve is, let's say I have 3 components: header, section, and footer with the following selectors:
<app-header>
<app-section>
<app-footer>
And then there are 6 buttons that will add or remove each component: Add Header, Add Section, and Add Footer
and when I click Add Header, the page will add <app-header> to the page that renders it, so the page will contain:
<app-header>
And then if I click Add Section twice, the page will now contain:
<app-header>
<app-section>
<app-section>
And if I click Add Footer, the page will now contain all these components:
<app-header>
<app-section>
<app-section>
<app-footer>
Is it possible to achieve this in Angular? Note that ngFor is not the solution I'm looking for, as it only allows to add the same components, not different components to a page.
EDIT: ngIf and ngFor is not the solution I'm looking for as the templates are already predetermined. What I am looking for is something like a stack of components or an array of components where we can add, remove, and change any index of the array easily.
EDIT 2: To make it more clear, let's have another example of why ngFor does not work. Let's say we have the following components:
<app-header>
<app-introduction>
<app-camera>
<app-editor>
<app-footer>
Now here comes a new component, <app-description>, which the user wants to insert in between and <app-editor>. ngFor works only if there is one same component that I want to loop over and over. But for different components, ngFor fails here.
What you're trying to achieve can be done by creating components dynamically using the ComponentFactoryResolver and then injecting them into a ViewContainerRef. One way to do this dynamically is by passing the class of the component as an argument of your function that will create and inject the component.
See example below:
import {
Component,
ComponentFactoryResolver, Type,
ViewChild,
ViewContainerRef
} from '#angular/core';
// Example component (can be any component e.g. app-header app-section)
import { DraggableComponent } from './components/draggable/draggable.component';
#Component({
selector: 'app-root',
template: `
<!-- Pass the component class as an argument to add and remove based on the component class -->
<button (click)="addComponent(draggableComponentClass)">Add</button>
<button (click)="removeComponent(draggableComponentClass)">Remove</button>
<div>
<!-- Use ng-template to ensure that the generated components end up in the right place -->
<ng-template #container>
</ng-template>
</div>
`
})
export class AppComponent {
#ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;
// Keep track of list of generated components for removal purposes
components = [];
// Expose class so that it can be used in the template
draggableComponentClass = DraggableComponent;
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
}
addComponent(componentClass: Type<any>) {
// Create component dynamically inside the ng-template
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentClass);
const component = this.container.createComponent(componentFactory);
// Push the component so that we can keep track of which components are created
this.components.push(component);
}
removeComponent(componentClass: Type<any>) {
// Find the component
const component = this.components.find((component) => component.instance instanceof componentClass);
const componentIndex = this.components.indexOf(component);
if (componentIndex !== -1) {
// Remove component from both view and array
this.container.remove(this.container.indexOf(component));
this.components.splice(componentIndex, 1);
}
}
}
Notes:
If you want to make it easier to remove the components later on, you can keep track of them in a local variable, see this.components. Alternatively you can loop over all the elements inside the ViewContainerRef.
You have to register your component as an entry component. In your module definition register your component as an entryComponent (entryComponents: [DraggableComponent]).
Running example:
https://plnkr.co/edit/mrXtE1ICw5yeIUke7wl5
For more information:
https://angular.io/guide/dynamic-component-loader
Angular v13 or above - simple way to add dynamic components to DOM
parent.component.html
<ng-template #viewContainerRef></ng-template>
parent.component.ts
#ViewChild("viewContainerRef", { read: ViewContainerRef }) vcr!: ViewContainerRef;
ref!: ComponentRef<YourChildComponent>
addChild() {
this.ref = this.vcr.createComponent(YourChildComponent)
}
removeChild() {
const index = this.vcr.indexOf(this.ref.hostView)
if (index != -1) this.vcr.remove(index)
}
Angular v12 or below
I have created a demo to show the dynamic add and remove process.
The parent component creates the child components dynamically and removes them.
Click for demo
Parent Component
// .ts
export class ParentComponent {
#ViewChild("viewContainerRef", { read: ViewContainerRef })
VCR: ViewContainerRef;
child_unique_key: number = 0;
componentsReferences = Array<ComponentRef<ChildComponent>>()
constructor(private CFR: ComponentFactoryResolver) {}
createComponent() {
let componentFactory = this.CFR.resolveComponentFactory(ChildComponent);
let childComponentRef = this.VCR.createComponent(componentFactory);
let childComponent = childComponentRef.instance;
childComponent.unique_key = ++this.child_unique_key;
childComponent.parentRef = this;
// add reference for newly created component
this.componentsReferences.push(childComponentRef);
}
remove(key: number) {
if (this.VCR.length < 1) return;
let componentRef = this.componentsReferences.filter(
x => x.instance.unique_key == key
)[0];
let vcrIndex: number = this.VCR.indexOf(componentRef as any);
// removing component from container
this.VCR.remove(vcrIndex);
// removing component from the list
this.componentsReferences = this.componentsReferences.filter(
x => x.instance.unique_key !== key
);
}
}
// .html
<button type="button" (click)="createComponent()">
I am Parent, Create Child
</button>
<div>
<ng-template #viewContainerRef></ng-template>
</div>
Child Component
// .ts
export class ChildComponent {
public unique_key: number;
public parentRef: ParentComponent;
constructor() {
}
remove_me() {
console.log(this.unique_key)
this.parentRef.remove(this.unique_key)
}
}
// .html
<button (click)="remove_me()">I am a Child {{unique_key}}, click to Remove</button>