How to random generate a component then display it in Angular? - javascript

not sure whether what I am trying to do is possible but I would like to click the generate button which then returns a random item from the array and once that array has been returned, I would like to connect it to a div in my parent component which then returns the specific child component only.
MY HTML:
<div class="components" *ngFor="let component of components" >
<div class="brother" *ngIf="">
<app-brother></app-brother>
</div>
<div class="sister">
<app-sister></app-sister>
</div>
<div class="baby">
<app-baby></app-baby>
</div>
</div>
MY TS
export class ParentComponent {
#Input() component: string | undefined;
components = [
'brother',
'sister',
'baby'
];
getRandom(){
let myChild = this.components[Math.floor(Math.random() * this.components.length)];
console.log(myChild);
}
}

You can insert a component dynamicall but here it would be better to use a NgSwitch statement.
<div class="components"[ngSwitch]="component" >
<div class="brother" *ngSwitchCase="brother">
<app-brother></app-brother>
</div>
<div class="sister" *ngSwitchCase="sister">
<app-sister></app-sister>
</div>
<div class="baby" *ngSwitchCase="baby">
<app-baby></app-baby>
</div>
</div>
TS:
export class ParentComponent {
public component: string;
components = [
'brother',
'sister',
'baby'
];
getRandom(){
this.component = this.components[Math.floor(Math.random() * this.components.length)];
}
}

Related

Why does my two separate Vue components override each others data?

Use case
Given the following vue component:
<template>
<div>
<slot>{{ title }}</slot>
<ul>
<li v-for="label in labels" :key="label">
<input
type="checkbox"
v-model="checked"
:label="label"
:id="label"
:value="label"
/>
<label :for="label">{{ label }}</label>
</li>
</ul>
</div>
</template>
<script>
import Component from "vue-class-component";
import Vue from "vue";
#Component({
props: {
labels: {
type: Array,
},
title: {
type: String,
},
},
watch: {
checked: [
{
handler: "updateParent",
},
],
},
})
export default class CheckboxList extends Vue {
checked = [];
updateParent() {
this.$emit("update", this.checked);
}
}
</script>
The component will render a checkbox list of the labels prop it receives from the parent.
When two components are used within the same page, using a v-if toggle as follows:
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png" width="10%" />
<hr />
<button type="button" #click="state = 'numbers'">
Show number checklist
</button>
<button type="button" #click="state = 'letters'">
Show letter checklist
</button>
<CheckboxList
v-if="state === 'numbers'"
title="numbers"
:labels="numbers"
#update="checkedNumbers = $event"
></CheckboxList>
<CheckboxList
v-if="state === 'letters'"
title="letters"
:labels="letters"
#update="checkedLetters = $event"
></CheckboxList>
<div>
<span>Checked letters:</span> {{ checkedLetters }}
<span>Checked numbers:</span> {{ checkedNumbers }}
</div>
</div>
</template>
<script>
import Vue from "vue";
import HelloWorld from "./components/HelloWorld";
import CheckboxList from "./CheckboxList.vue";
import Component from "vue-class-component";
#Component({
components: {
CheckboxList,
},
})
export default class App extends Vue {
numbers = [1, 2, 3, 4, 5];
letters = ["a", "b", "c", "d"];
state = "numbers";
checkedLetters = [];
checkedNumbers = [];
}
</script>
This will create the following UI:
Why when toggling between the two components (using v-if) and checking the boxes - the data will mix up resulting the following behavior?
A Full working example can be found here: https://codesandbox.io/s/epic-resonance-plwvi?file=/src/App.vue
As part of Vues internal optimizations, it will strive to reuse components within its directives (i.e v-if or v-for for instance) as the default behavior.
This is documented in their docs.
Since unfortunately it's indeed what Vue will default to, consider adding a key prop with unique values in order to 'force' Vue to treat these as separate components:
<CheckboxList
key="numbers" <!-- Add this to distinct between the two -->
v-if="state === 'numbers'"
title="numbers"
:labels="numbers"
#update="checkedNumbers = $event"
></CheckboxList>
<CheckboxList
key="letters" <!-- Add this to distinct between the two -->
v-if="state === 'letters'"
title="letters"
:labels="letters"
#update="checkedLetters = $event"
></CheckboxList>
Note that this isn't required when components are rendered in one go - without conditional switches or looping:
<!--
Since both are rendered at the same time,
they will be considered separate components
and won't require a 'key' prop to distinguish between the two
-->
<CheckboxList
title="numbers"
:labels="numbers"
#update="checkedNumbers = $event"
></CheckboxList>
<CheckboxList
title="letters"
:labels="letters"
#update="checkedLetters = $event"
></CheckboxList>

calling child method in parent component

I have two components. One is Parent and second is child.
What I want to do is Add new element in child component(for now it is simple div).
It is working when I click button (Add pallet) in child html.
When I click the button (Add pallet) on parent HTML it's not working.
It's creating a new instance of pallets but this div doesn't show on child HTML. Result in console.log is the same.
Child component:
import { Component } from "#angular/core";
import { Pallet } from '../pallet.model';
#Component({
selector: 'app-workspace',
templateUrl: './workspace.component.html',
styleUrls: ['./workspace.component.css']
})
export class WorkspaceComponent {
pallets: Pallet[] = [];
addPallet1(){
let temp = new Pallet();
temp.palletName = 'Pallet'+(this.pallets.length+1);
temp.id=(this.pallets.length+1).toString();
temp.width = "500";
this.pallets.push(temp);
console.log(temp); /*=>>
Pallet {palletName: "Pallet1", id: "1", width: "500"}
id: "1"
palletName: "Pallet1"
width: "500"
__proto__: Object
*/
}
ngOnInit(){}
Child HTML:
<div id="workspace" class="workspace">
<div class="workspace-box" cdkDrag >
<button (click) = "addPallet1()">Add Pallet</button>
<button (click) = "check()">check</button>
<div class="pallet" *ngFor="let pallet of pallets; let index=index" cdkDrag [ngStyle]="{'width.px': pallet.width }">
<div
[(ngModel)]="pallet.palletName"
name="{{pallet.palletName}}"
id="{{pallet.id}}"
ngDefaultControl>
<label>{{pallet.palletName}}</label>
</div>
</div>
<div>
parent html:
<button (click)="appChildWorkspace.addPallet1()">Add Pallet</button>
<app-workspace #appChildWorkspace></app-workspace>

How do I pass data to a component using Props in Vue2?

I have created a .Vue file to feature information on a cafe (Cafe Details Page). However, I would like to take parts of this details page and make it its own component, in order to manage any template updates more efficiently.
Therefore, I have created a Component (CafeHeader.vue) inside a components folder. I am trying to pass down the data from my array (Which is being used on my Cafe Details page) to this component using Props. However, I can't seem to get it to work.
The template for my Cafe Details Page is as below:
<template>
<div>
<div v-for="cafe in activeCafe">
<CafeHeader v-bind:cafes="cafes" />
<div class="content">
<p>{{ cafe.cafeDescription }}</p>
</div>
</div>
</div>
</template>
<script>
import CafeHeader from "./../../components/CafeHeader";
import cafes from "./../../data/cafes"; // THIS IS THE ARRAY
export default {
data() {
return {
cafes: cafes
};
},
components: {
CafeHeader,
},
computed: {
activeCafe: function() {
var activeCards = [];
var cafeTitle = 'Apollo Cafe';
this.cafes.forEach(function(cafe) {
if(cafe.cafeName == cafeTitle){
activeCards.push(cafe);
}
});
return activeCards;
}
}
};
</script>
Then, in a components folder I have a component called CafeHeader where I am wanting to use the data from the array which is previously imported to the Cafe Details page;
<template>
<div>
<div v-for="cafe in cafes">
<h1>Visit: {{cafe.cafeName}} </h1>
</div>
</div>
</template>
<script>
export default {
name: "test",
props: {
cafes: {
type: Array,
required: true
}
},
data() {
return {
isActive: false,
active: false
};
},
methods: {}
};
</script>
If in the CafeHeader component I have cafe in cafes, it does render data from the cafes.js However, it is every cafe in the list and I want just a single cafe.
<template>
<div>
<div v-for="cafe in cafes">
<h1>Visit: {{cafe.cafeName}} </h1>
</div>
</div>
</template>
The component also needed activeCafes on the v-for
<template>
<div>
<div v-for="cafe in activeCafes">
<h1>Visit: {{cafe.cafeName}} </h1>
</div>
</div>
</template>

How to pass formarray to other component

I want to pass formarray to child component to display formarray values there. Below is the code, what I've tried so far. I am not able to find the way to display the formarray values in the child component.
app.component.html
<div [formGroup]="userForm">
<div formArrayName="users">
<div *ngFor="let user of users.controls; let i = index">
<input type="text" placeholder="Enter a Room Name" [formControlName]="i">
</div>
</div>
</div>
<button (click)="addUser()">Add Room</button>
<title [users]="users"></title>
app.component.ts
userForm: FormGroup;
constructor(private fb: FormBuilder) {}
public get users(): any {
return this.userForm.get('users') as FormArray;
}
ngOnInit() {
this.userForm = this.fb.group({
users: this.fb.array([this.fb.control('')])
});
}
addUser() {
this.users.push(this.fb.control(''));
}
title.component.html
<div *ngFor="let user of users.controls">{{ user.value }}</div>
title.component.ts
#Input() users;
ngOnChanges(changes) {
console.log(changes);
}
But above code not displaying the formarray values in the child component.
Example stackblitz is here
title is a keyword and its already defined tag with HTML. just use some different name for the component selector
selector: 'title1',
STACKBLITZ DEMO

Vue.js - Using parent data in component

How I can get access to parent's data variable (limitByNumber) in my child component Post?
I tried to use prop but it doesn't work.
Parent:
import Post from './components/Post.vue';
new Vue ({
el: 'body',
components: { Post },
data: {
limitByNumber: 4
}
});
Component Post:
<template>
<div class="Post" v-for="post in list | limitBy limitByNumber">
<!-- Blog Post -->
....
</div>
</template>
<!-- script -->
<script>
export default {
props: ['list', 'limitByNumber'],
created() {
this.list = JSON.parse(this.list);
}
}
</script>
Option 1
Use this.$parent.limitByNumber from child component. So your Component template would be like this
<template>
<div class="Post" v-for="post in list | limitBy this.$parent.limitByNumber" />
</template>
Option 2
If you want to use props, you can also achieve what you want. Like this.
Parent
<template>
<post :limit="limitByNumber" />
</template>
<script>
export default {
data () {
return {
limitByNumber: 4
}
}
}
</script>
Child Pots
<template>
<div class="Post" v-for="post in list | limitBy limit">
<!-- Blog Post -->
....
</div>
</template>
<script>
export default {
props: ['list', 'limit'],
created() {
this.list = JSON.parse(this.list);
}
}
</script>
If you want to access some specific parent, you can name all components like this:
export default {
name: 'LayoutDefault'
And then add some function (maybe like vue.prototype or Mixin if you need it in all your components). Something like this should do it:
getParent(name) {
let p = this.$parent;
while(typeof p !== 'undefined') {
if (p.$options.name == name) {
return p;
} else {
p = p.$parent;
}
}
return false;
}
and usage could be like this:
this.getParent('LayoutDefault').myVariableOrMethod

Categories

Resources