Toggling the visibility of a component with an Input function in Angular - javascript

I have a component that accepts a callback function #Input() closeCallback: () => void;
and calls it in a close function like this :
close() {
this.closeCallback();
}
<button class="close" (click)="close()">
<img src="assets/img/share/icon_close_dark.svg" />
</button>
So in the parent component I passed a function to it :
<ng-container *ngIf="isPopupOpen">
<e2-fullscreen-popup
[title]="'top promotions'"
[closeCallback]="closePopup"
>
...
</e2-fullscreen-popup>
</ng-container>
closePopup() {
this.isPopupOpen = false;
console.log('this.isPopupOpen',this.isPopupOpen);
}
so why I'm not able to remove the component when clicking on the button, even thou the logs themselves show that this.isPopupOpen is false?

#Input is used to pass data to the child component; to implement a callback you need to use #Output.
In your child component:
#Output() closeCallback = new EventEmitter();
// ...
close() {
this.closeCallback.emit();
}
In your parent component:
<ng-container *ngIf="isPopupOpen">
<e2-fullscreen-popup
[title]="'top promotions'"
(closeCallback)="closePopup($event)"
>
...
</e2-fullscreen-popup>
</ng-container>
closePopup(dataFromChild: any){
// handle callback
this.isPopupOpen = false;
console.log('this.isPopupOpen',this.isPopupOpen);
}

Related

Access scoped slot from component

I got this component:
<template>
<Popover v-slot="{ open }">
<PopoverButton>
{{ title }}
</PopoverButton>
<div v-if="computedOpen">
<PopoverPanel static>
<slot name="popover"></slot>
</PopoverPanel>
</div>
</Popover>
</template>
<script>
import {Popover, PopoverButton, PopoverPanel} from '#headlessui/vue'
import {computed, ref, watch} from 'vue'
import {useRoute} from 'vue-router'
export default {
name: 'DropdownMenuButton',
mixins: [slots],
props: {
name: {
type: String,
},
title: {
type: String,
default: ''
},
},
components: {
Popover,
PopoverButton,
PopoverPanel,
ChevronDownIcon,
},
setup(props) {
const isOpen = ref(null);
const route = useRoute()
watch(route, () => {
isOpen.value = false;
});
const computedOpen = computed(() => {
let open = ...? //this is missing...
return isOpen.value && open.value;
});
return {
computedOpen
}
},
}
</script>
This component makes use of headless UI's popover.
Now I'd like to close the popover once the route changes. While the route-change is being detected fine, I can not access the <Popover>'s open value in the setup() method to determine, whether computedOpen should return true or false.
My question: How can I access v-slot="{ open } in the computed value?
What you want is not possible.
Think about it:
Everything inside <Popover> element (the slot content) is compiled by Vue to a function returning the virtual DOM representation of the content
This function is passed to the Popover component as slots.default and when the Popover component is rendering, it calls that function passing the open value as an argument
So open value is Popover component's internal state and is only accessible inside the template (ie. slot render function)
So I think your best bet is throw away the computed idea and just use the open value directly in the template or use method instead, passing open as an argument
<div v-if="isOpen && open">
<PopoverPanel static>
<slot name="popover"></slot>
</PopoverPanel>
</div>
<div v-if="isPanelOpen(open)">
<PopoverPanel static>
<slot name="popover"></slot>
</PopoverPanel>
</div>

Angular - how to pass callback in menu item on parent component menu

I want to have a menu in my app bar that can get its menu items from child components. I am working with the Angular Material "mat-menu" and I'm able to display the menu item but I can't seem to fire off the associated function on the child component.
The app.component.html (parent):
<div>
<mat-toolbar style="display: flex; flex-direction: row; justify-content: space-between; margin-bottom: 12px">
<div>
<button type="button" mat-icon-button id="btnMore" [matMenuTriggerFor]="appMenu" [matMenuTriggerData]="menuData">
<mat-icon>more_horiz</mat-icon>
</button>
<mat-menu #appMenu="matMenu" xPosition="before">
<ng-template matMenuContent let-aliasMenuItems="menuItems">
<button mat-menu-item *ngFor="let item of aliasMenuItems" (click)="handleMenuAction(item.action)">
{{item.text}}
</button>
</ng-template>
</mat-menu>
</div>
</mat-toolbar>
</div>
<div>
<router-outlet></router-outlet>
</div>
Here is app.component.ts (parent). It retrieves the menu data from the appService component. It also (should) execute the callback.
ngOnInit() {
this.appService.getMenuData().subscribe(menuData => this.menuData = menuData);
}
handleMenuAction(action: Function) {
action();
}
Here is the child component "company.component.ts" which passes its menu items to app.service so they can be retrieved by app.component. Notice the menuData is an object that contains an array of objects of types string and callback function.
ngOnInit(): void {
this._appService.setMenuData({
menuItems: [
{text: "Add Company", action: this.addCompany}
]});
}
addCompany(): void {
this._router.navigate(['/company', 0])
}
For some reason the click event handler is not showing up in my Chrome dev tools. I would like the menu clicks to call functions, not just perform navigation.
There may be a better way to solve this problem. If so, please provide a link to an example. TIA.
Edit: Stackblitz is at https://stackblitz.com/edit/angular-nbzoe6
Updated Answer
According to your stackblitz:
You just only need to bind the this of child component to new menu actions to run in child scope when you decide to call menu action
something like this:
company-list.component.ts
ngOnInit(): void {
this._appService.setMenuData({
menuItems: [
{text: "Add Company", action: this.addCompany.bind(this)}
]});
}
Old Answer
You can create the menuItems$ observable in your appService and subscribe on it in app.component.ts and from child component you just add new menuItems to this observable, The menuItems in your app.component will have new value
Something like this
appService.ts
class AppService {
// ...
menuItems$: BehaviourSubject<any[]> = new BehaviourSubject([]);
constrcutor() {}
// ...
}
app.component.ts
class AppComponenet {
// ...
menuItems: any[] = [];
constrcutor(private appService: AppService) {}
ngOnInit() {
// ...
this.appService.menuItems$.subscribe(newMenu => {
this.menuItems = newMenu;
});
}
}
child.compnenet.ts
class ChildComponenet {
// ...
constrcutor(private appService: AppService) {}
ngOnInit() {
// ...
this.appService.menuItems$.new(['home', 'about']);
}
}

Angular 6 - MatDialog - EventEmitter - share object to parent component from MatDialog

Right now able to communicate between two components. but don't know how to pass user (selected) entered value as Object via event emitter from MatDialog component to the parent component. Here I want to pass selected option value and text area value as object after clicking the submit button.
dialog.html
<mat-select [(ngModel)]="selectedIssue" [ngModelOptions]="{standalone: true}">
<mat-option [value]="option1">Option AA</mat-option>
<mat-option [value]="option2">Option BB</mat-option>
<mat-option [value]="option3">Option CC</mat-option>
<mat-option [value]="option4">Option DD</mat-option>
<mat-option [value]="option5">Option EE</mat-option>
</mat-select>
<div>
<textarea class="mention-reason" [(ngModel)]="reason" [ngModelOptions]="{standalone: true}"></textarea>
</div>
<button class="cancel" matDialogClose>Cancel</button>
<button class="submit" [mat-dialog-close] (click)="submitUserReason()">Submit</button></span>
dialog.ts
onSubmitReason = new EventEmitter();
submitUserReason(): void {
this.onSubmitReason.emit('hello');
}
onConfirmClick(): void {
this.dialogRef.close(true);
}
parent.ts
callSupport() {
const dialogRef = this.dialog.open(customerSupportComponent);
const subscribeDialog = dialogRef.componentInstance.onSubmitReason.subscribe((data) => {
console.log('dialog data', data);
//i can see 'hello' from MatDialog
});
dialogRef.afterClosed().subscribe(result => {
subscribeDialog.unsubscribe();
});
Thanks so much whoever helping.
I assume there are two buttons. 1) submit 2) close
So, If you want selected data in parent component on submit button click then,
submitUserReason(): void {
this.dialogRef.close({ option: userSelectedOption, reason:userReason });
}
And in parent component,
dialogRef.afterClosed().subscribe(result => {
console.log(result);
});
In your dialog.ts you want to pass your selected option instead of just a string. Something like:
submitUserReason(): void {
this.onSubmitReason.emit(selectedIssue);
}
You can emit whatever you want (depending on how you've typed things) so if you'd like to pass more data you could also pass an object:
submitUserReason(): void {
let data = { issue : selectedIssue, reason: userReason};
this.onSubmitReason.emit(data);
}

VueJS 2 - How to Pass Parameters Using $emit

I am working on a modal component using VueJS 2. Right now, it basically works -- I click on a button and the modal opens, etc.
What I want to do now is create a unique name for the modal and associate the button with that particular button.
This is what I have in mind. The modal has a unique name property:
<modal name='myName'>CONTENT</modal>
And this would be the associate button:
<button #click="showModal('myName')"></button>
What I need to figure out is how to pass the parameter of showModal to the modal component.
Here is the method that I'm using in the root vue instance (i.e, NOT inside my modal component):
methods: {
showModal(name) { this.bus.$emit('showModal'); },
}
What I want to do is to access the name property in the component -- something like this:
created() {
this.bus.$on('showModal', () => alert(this.name));
}
But this shows up as undefined.
So what am I doing wrong? How can I access the name property inside the modal component?
NOTE: If you are wondering what this.bus.$on is, please see the following answer to a previous question that I asked: https://stackoverflow.com/a/42983494/7477670
Pass it as a parameter to $emit.
methods: {
showModal(name) { this.bus.$emit('showModal', name); },
}
created() {
this.bus.$on('showModal', (name) => alert(name));
}
Also, if you want to give the modal a name, you need to accept it as a prop in the modal component.
Vue.component("modal",{
props:["name"],
...
})
Then I assume you will want to do something like,
if (name == this.name)
//show the modal
<!-- File name is dataTable.vue -->
<template>
<div>
<insertForm v-on:emitForm="close"></insertForm>
</div>
</template>
<script>
import InsertForm from "./insertForm";
import Axios from "axios";
export default {
components: {
InsertForm
},
data: () => ({
}),
methods: {
close(res) {
console.log('res = ', res);
}
}
};
</script>
<!-- File name is insertForm.vue -->
<template>
<div>
<v-btn #click.native="sendPrameter">
<v-icon>save</v-icon>
</v-btn>
</div>
</template>
<script>
export default {
data: () => ({
mesage:{
msg:"Saved successfully",
color:'red',
status:1
}
}),
methods: {
sendPrameter: function() {
this.$emit("emitForm", this.mesage);
}
}
};
</script>
https://vuejs.org/v2/guide/components-custom-events.html

Parent onChange sets child property to true

Form (parent) component:
export default {
template: `
<form name="form" class="c form" v-on:change="onChange">
<slot></slot>
</form>`,
methods: {
onChange:function(){
console.log("something changed");
}
}
};
and a c-tool-bar component (child) that is (if existing) inside the c-form's slot
export default {
template: `
<div class="c tool bar m006">
<div v-if="changed">
{{ changedHint }}
</div>
<!-- some other irrelevant stuff -->
</div>`,
props: {
changed: {
type: Boolean,
default: false,
},
changedHint: String
}
};
What I want to achieve is, that when onChange of c-form gets fired the function
checks if the child c-tool-bar is present && it has a changedHint text declared (from backend) it should change the c-tool-bar's prop "changed" to true and the c-bar-tool updates itself so the v-if="changed" actually displays the changedHint. I read the vue.js docs but I don't really know where to start and what the right approach would be.
You can use the two components like this:
<parent-component></parent-component>
You will be passing in the :changed prop a variable defined in the parent component in data(). Note the :, we are using dynamic props, so once the prop value is changed in the parent it will update the child as well.
To change the display of the hint, change the value of the variable being passed in as prop to the child component:
export default {
template: `
<form name="form" class="c form" v-on:change="onChange">
<child-component :changed="shouldDisplayHint"></child-component>
</form>`,
data() {
return {
shouldDisplayHint: false,
};
},
methods: {
onChange:function(){
this.shouldDisplayHint = true; // change the value accordingly
console.log("something changed");
}
}
};
One simple way is to use $refs.
When you will create your child component inside your parent component, you should have something like: <c-tool-bar ref="child"> and then you can use it in your parent component code:
methods: {
onChange:function(){
this.$refs.child.(do whatever you want)
}
}
Hope it helps but in case of any more questions: https://v2.vuejs.org/v2/guide/components.html#Child-Component-Refs

Categories

Resources