I want to scroll to a particular div on clicking a button in Angular 7, below is the code I am using but it is working in stackblitz but showing error when i use in my project.
"Cannot read property 'scrollIntoView' of undefined".
https://stackblitz.com/edit/angular-scroll-local-variable?file=src%2Fapp%2Fscroll.component.ts
try this link: https://stackblitz.com/edit/angular-scroll-local-variable-ja96uz?file=src%2Fapp%2Fapp.component.html
<button (click)="scroll(target)"></button>
<div #target>Your target</div>
and in component:
scroll(el) {
el.scrollIntoView();
}
Try angular ViewportScroller Service Which provide scrollToAnchor method
scroll.html
<button (click)="scroll('target')">Click to scroll</button>
<div id="target">Your target</div>
scroll.ts
import { Component, Input } from '#angular/core';
import { ViewportScroller } from '#angular/common';
#Component({
selector: 'scroll',
template: `
<button (click)="scroll('target')">Click to scroll</button>
<div id="target">Your target</div>
`,
styles: [`h1 { font-family: Lato; }`, `div { margin-top: 5000px; }`]
})
export class ScrollComponent {
constructor(private vps: ViewportScroller) {
}
scroll(id) {
this.vps.scrollToAnchor(id);
}
}
Example:https://stackblitz.com/edit/angular-scroll-local-variable-99hwvo
Try using ViewChild:
//HTML
<button (click)="scroll()"></button><div #target>Your target</div>
//ts
//Import
import { ..., ViewChild, ElementRef } from '#angular/core';
//Declare
#ViewChild('target') targetEl: ElementRef;
scroll() {
this.targetEl.nativeElement.scrollIntoView();
}
Scroll.html
<button (click)="scroll()">Click to scroll</button>
<div id="target">Your target</div>
componet.ts
getOffset(el) {
el = el.getBoundingClientRect();
return {
left: el.left + window.scrollX,
top: el.top + window.scrollY,
bottom: el.top + window.scrollY
}
}
scroll() {
var scroll_to = document.getElementById('target');
var topHight = this.getOffset(scroll_to).top;
window.scrollTo(0, topHight);
}
The code is not working because of *ngIf condition, when you use show variable is default set to false, hence the div is not rendered on the component.
The code should be used in a related component where you want to scroll to be done, for example:
if you required in the scroll component then
HTML:
<button (click)="scroll(target)">clicking this button</button>
<div style="marging-top: 100px; height: 900px;"></div>
<div #target *ngIf="show" style="border: 1px solid #000; padding: 10px;margin-top: 10px;">
show get screen scrolled to this div
</div>
TS:
scroll(el: HTMLElement) {
if(el){ // If the div is rendered on the HTML then it should be HTML element
el.scrollIntoView();
}
}
StackBlitz
Related
There is a product page where product list component and action buttons are there. This is the parent component. That product list component consists of list table and edit/add modal. Now, problem is Add action event is in parent component. But, add modal related data is available in child component.
So, how can i open that model based on click event from parent? Here i am doing like this approach.
Parent Component(Product Component snippets )
<template>
..... other code ....
<div class="action-buttons">
<vu-button class="add-action" #click="onAddAction">
<svg-icon
fill="#0071E3"
name="add"
height="20"
width="28"
/>
</vu-button>
</div>
<ChildComponent :open-add-modal="isAddModal" />
</template>
Methods in Parent component
onAddAction() {
this.editable = false;
this.isAddModal = true;
},
Now, in child component i passing boolean props openAddModal but i am checking condition into created hook to show Add modal.
Problem is in initial rendering or page load add modal is showing up not in click event. How can i solve this issue?
Child component(Created hook)
created() {
if(this.openAddModal) {
this.showModal = true;
this.formType = 'add';
this.editId = null;
}
},
I want to show add modal based on click event from parent not in initial page load.
You can try using a watcher instead of checking the value of open-add-modal in the created hook. This way, when the prop open-add-modal in the child component changes, you can check the new value there and emit the needed data to the parent to then open the modal.
Example:
Parent component code
<template>
<div>
<p>Parent component</p>
<button #click="changeOpenAddModal">Clic to get child data</button>
<button #click="resetParent">Reset data in parent</button>
<p>Data from child: {{ childData }}</p>
<br />
<br />
<Child
:openAddModal="this.openAddModal"
#child-component-data-emit="this.setChildData"
/>
</div>
</template>
<script>
import Child from "./Child";
export default {
name: "Parent",
components: { Child },
data() {
return {
childData: null,
openAddModal: false,
};
},
methods: {
changeOpenAddModal() {
this.openAddModal = !this.openAddModal;
console.log(
"changing openAddModal data. New value is ",
this.openAddModal
);
},
setChildData(data) {
console.log("setting child data", data);
this.childData = data;
},
resetParent() {
this.childData = null;
this.changeOpenAddModal();
},
},
};
</script>
Child component code
<template>
<div>
<p>Child component</p>
</div>
</template>
<script>
export default {
name: "Child",
props: {
openAddModal: {
type: Boolean,
default: false,
},
},
data() {
return {
childData: {
prop1: "lorem",
prop2: "ipsum",
prop3: "dolor",
},
};
},
watch: {
openAddModal: function (newValue, oldValue) {
console.log("child watcher with newValue", newValue);
if (newValue) {
this.$emit("child-component-data-emit", this.childData);
}
},
},
mounted: function () {
console.log("prop openAddModal value on mounted:", this.openAddModal);
},
};
</script>
I have built a few modals with Vue 2 and Vue CLI, and use an alternate approach for showing, hiding, and determined if add or edit mode. No watch or separate add/edit mode boolean is necessary.
The 'product' processing is somewhat contrived since no database or AJAX are used in this example, but you should get be able to evaluate the functionality.
Parent.vue
<template>
<div class="parent">
<h4>Parent of Form Modal</h4>
<div class="row">
<div class="col-md-6">
<button class="btn btn-secondary" #click="showAddModal">Show Add Modal</button>
<button class="btn btn-secondary btn-edit" #click="showEditModal">Show Edit Modal</button>
</div>
</div>
<form-modal v-if="displayModal"
:parentProduct="product"
#save-product-event="saveProduct"
#close-modal-event="hideModal"
/>
</div>
</template>
<script>
import FormModal from './FormModal.vue'
export default {
components: {
FormModal
},
data() {
return {
product: {
id: 0,
name: '',
description: ''
},
displayModal: false
}
},
methods: {
showAddModal() {
this.resetProduct();
this.displayModal = true;
},
showEditModal() {
this.product.id = 1;
this.product.name = 'productEdit';
this.product.description = 'productEditDescription';
this.displayModal = true;
},
hideModal() {
this.displayModal = false;
},
saveProduct(modalProduct) {
this.product = modalProduct;
this.hideModal();
console.log(this.product);
},
resetProduct() {
this.product.id = 0;
this.product.name = '';
this.product.description = '';
}
}
}
</script>
<style scoped>
.btn-edit {
margin-left: 0.5rem;
}
</style>
FormModal.vue
<template>
<!-- The Modal -->
<div id="form-modal" class="modal-dialog-container">
<div class="modal-dialog-content">
<div class="modal-dialog-header">
<h4>{{ modalTitle }}</h4>
</div>
<div class="modal-dialog-body">
<form #submit.prevent="saveProduct">
<div class="form-group">
<label for="product-name">Name</label>
<input type="text" class="form-control" id="product-name" v-model="product.name">
</div>
<div class="form-group">
<label for="product-description">Description</label>
<input type="text" class="form-control" id="product-description" v-model="product.description">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
<button type="button" class="btn btn-secondary btn-close" #click="closeModal">Cancel</button>
</form>
</div>
</div>
</div>
</template>
<script>
export default {
props: {
parentProduct: {
type: Object,
required: true
}
},
data() {
return {
product: this.parentProduct
}
},
computed: {
modalTitle() {
return this.product.id === 0 ? 'Add Product' : 'Edit Product';
}
},
methods: {
closeModal() {
this.$emit('close-modal-event');
},
saveProduct() {
// Add product
if (this.product.id === 0) {
this.product.id = 2;
}
this.$emit('save-product-event', this.product);
}
}
}
</script>
<style scoped>
.modal-dialog-container {
/* display: none; Hidden by default */
position: fixed;
/* Stay in place */
z-index: 1;
/* Sit on top */
left: 0;
top: 0;
width: 100%;
/* Full width */
height: 100%;
/* Full height */
overflow: auto;
/* Enable scroll if needed */
background-color: rgb(0, 0, 0);
/* Fallback color */
background-color: rgba(0, 0, 0, 0.4);
/* Black w/ opacity */
}
.modal-dialog-content {
background-color: #fefefe;
margin: 10% auto;
padding: 20px;
border: 1px solid #888;
border-radius: 0.3rem;
width: 30%;
}
.btn-close {
margin-left: 0.5rem;
}
</style>
I have a spinner , but I also want some text in the spinner independent of the component. So for example if you are doing a save action, then the spinner will showing: ...saving. But for example if you do a search action. The spinner will showing: ..processing and so on.
I have this as spinner component:
<div *ngIf="(isLoading | async)" class="overlay">
<div>
<mat-progress-spinner class="spinner" [color]="color" [mode]="mode" [value]="value"> </mat-progress-spinner>
<div style="position:relative; top: -60px; left: 30px;">{{message}}</div>
</div>
</div>
and ts script:
export class LoaderComponent {
color = 'primary';
mode = 'indeterminate';
value = 50;
message = 'Hello there';
isLoading: Subject<boolean> = this.loaderService.loaderState;
constructor(private loaderService: LoaderService) {}
}
and then for example I load the spinner in this component: listComponent
<app-loader ></app-loader>
So I see the spinner but not the message.
So what I have to improve so that the text will also been shown?
And how to make the text dynamically? So that you can put any text message in it?
Thank you
I have it now like this:
<div *ngIf="(isLoading | async)" class="overlay">
<div>
<mat-progress-spinner class="spinner" [color]="color" [mode]="mode" [value]="value"> </mat-progress-spinner>
<div style="position:absolute; top: -60px; left: 30px;">{{message}}</div>
</div>
</div>
export class LoaderComponent {
color = 'primary';
mode = 'indeterminate';
value = 50;
#Input()
message = 'Hello there';
isLoading: Subject<boolean> = this.loaderService.loaderState;
constructor(private loaderService: LoaderService) {}
}
and this is the css:
.overlay {
position:fixed;
display:block;
width:100%;
height:100%;
top:0;
left:0;
background-color:rgba(74,74,74,.8);
z-index:99999;
}
.spinner {
margin:auto;
position:absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
}
You can find Working demo here in this StackBlitz Link
You have to use Behavior Subject and service...
Your app.component.html is..
<mat-toolbar color="primary">Spinner Demo</mat-toolbar>
<div style="margin:1em">
<button style="margin:1rem" mat-raised-button color="primary" (click)="spinner('search')">Search</button>
<button mat-raised-button color="primary" (click)="spinner('Send')">Send</button>
<button style="margin:1rem" mat-raised-button color="primary" (click)="spinner('Save')">Save</button>
</div>
<app-search-output ></app-search-output>
Your app.component.ts is...
export class AppComponent {
constructor( private serachService: SearchService){
}
spinner(term){
this.serachService.sendData(term);
}
ngOnInit(){
}
}
Your Service where you can use behaviorSubject as like this...
export class SearchService {
searchData$: BehaviorSubject<object[]> = new BehaviorSubject<object[]>([{}]);
constructor() { }
sendData(term){
this.searchData$.next(term)
}
getData(){
return this.searchData$.asObservable();
}
}
Your spinner component HTML is...
<div style="margin:2em auto; padding:1rem; box-shadow: 1px 2px 7px red; width:50vw">
<span style="margin:1em; padding:1rem">{{bookData$ |async |json}}</span>
<mat-spinner style="margin:1em" color="warn" ></mat-spinner>
</div>
Your spinner.component.ts is ...
export class SearchOutputComponent implements OnInit {
bookData$;
constructor(private searchService: SearchService) { }
ngOnInit() {
this.bookData$ = this.searchService.getData();
}
}
You can pass text values as #Input to your component and use the component as below
I believe you are not able to see it because of css issue. Try making the text position:absolute.
<div *ngIf="(isLoading | async)" class="overlay">
<div>
<mat-progress-spinner class="spinner" [color]="color" [mode]="mode" [value]="value"> </mat-progress-spinner>
<div style="position:absolute; top: -60px; left: 30px;">{{message}}</div>
</div>
</div>
To further debug please create a stackblitz.
<app-loader [message] = "Processing"></app-loader>
Spinner Component
import { Component, Input, OnInit } from '#angular/core';
export class LoaderComponent {
color = 'primary';
mode = 'indeterminate';
value = 50;
#Input()
message = 'Hello there'; // Default text will be Hello there but if you pass anything as stated above then it will be replaced.
isLoading: Subject<boolean> = this.loaderService.loaderState;
constructor(private loaderService: LoaderService) {}
}
Try to change style position to relative in your html file it should work:
HTML file
<div *ngIf="(isLoading | async)" class="overlay">
<div>
<mat-progress-spinner class="spinner" [color]="color" [mode]="mode" [value]="value"> </mat-progress-spinner>
<div style="position:relative; top: -60px; left: 30px;">{{message}}
</div>
</div>
</div>
TS file
export class ProgressSpinnerOverviewExample {
color = 'primary';
mode = 'indeterminate';
value = 50;
#Input()
message = 'Hello';
isLoading = true;
constructor() {}
}
I have 2 tabs,onclick a tab1 a div will be shown, again on click toggle button the div will expand(100% width) and collapse(25% width) by changing the class. Again when I click on tab2, and then click on tab1 my div should be remain collapse always,I mean its class should be 'old'.Here is the code below.
app.component.html
<span style="cursor:pointer" (click) = "tab1()">Tab1</span> <span (click) = "tab2()" style="cursor:pointer">Tab2</span>
<div [ngClass]="{'old': toggle, 'new': !toggle}" *ngIf="show" class="old">
Hello
</div>
<button (click)="change()">change</button>
app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
toggle:boolean = true;
show:any;
tab1(){
alert('tab1');
this.show = true;
}
tab2(){
alert('tab2');
this.show = true;
}
change(){
this.toggle = !this.toggle;
}
ngOnInit() {
this.show = false;
}
}
app.component.css
.old{
width:25%;
border:1px solid;
height:200px;
cursor:pointer;
background:yellow;
}
.new{
width:100%;
border:1px solid;
height:200px;
cursor:pointer;
background:green;
}
Try this:
HTML
<div [ngClass]="toggle ? 'old' : 'new'" *ngIf="show">
Hello
</div>
Removed the class="old". Please check now.
stackblitz demo
I'm using the latest version of Angular and Angular Material. I'm having issues with my components. The page Load like this:
Before de click
And the content just appear when I click on the menu.
After clicking
I already tried to uninstall and install all the meterial stuff. And this issue continues. I have a separate module to import and export all the material components. Here is the code of the component that is using the material tags:
profile.component.ts
import { Component, OnInit } from '#angular/core';
import {AuthService} from "../../services/auth.service";
import {User} from "../../model/model.user";
import {Router} from "#angular/router";
import { Expense } from '../../model/model.expense';
import { ReceiptService } from "../../services/receipt.service";
import { ExpenseService } from './../../services/expense.service';
import { Receipt } from './../../model/model.receipt';
#Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.css']
})
export class ProfileComponent implements OnInit {
totalReceita = 0;
totalDespesa = 0;
receipts = []
expenses = []
currentUser: User;
constructor(public authService: AuthService, public router: Router, public receiptService: ReceiptService, public expenseService: ExpenseService) {
this.currentUser = JSON.parse(localStorage.getItem('currentUser'));
}
ngOnInit() {
this.receiptService.getReceipts(this.currentUser.id).subscribe(
data => {
console.log(data)
this.receipts = this.retiraArrayRec(data);
this.somaTudoRec();
}
);
this.expenseService.getExpenses(this.currentUser.id).subscribe(
data => {
this.expenses =this.retiraArrayDesp(data);
this.somaTudoDes();
}
);
console.log(this.receipts)
}
retiraArrayRec(data){
let lista = []
data.forEach(element => {
let receita : Receipt = new Receipt;
receita.name = element[0];
receita.value = element[1]
lista.push(receita);
});
return lista;
}
retiraArrayDesp(data){
let lista = []
data.forEach(element => {
let despesa : Expense = new Expense;
despesa.name = element[0];
despesa.value = element[1]
lista.push(despesa);
});
return lista;
}
somaTudoRec(){
this.receipts.forEach(element => {
this.totalReceita += element.value;
});
}
somaTudoDes(){
this.expenses.forEach(element => {
this.totalDespesa += element.value;
});
}
// login out from the app
logOut() {
this.authService.logOut()
.subscribe(
data => {
this.router.navigate(['/login']);
},
error => {
});
}
}
profile.component.html
<mat-sidenav-container fullscreen class="menu-container">
<mat-sidenav #sidenav>
<mat-nav-list>
<a mat-list-item routerLink="/home" routerLinkActive="active-list-item">
<h2 matLine>Home</h2>
<mat-icon matListIcon>home</mat-icon>
</a>
<a mat-list-item routerLink="/account" routerLinkActive="active-list-item">
<h2 matLine>Receitas</h2>
<mat-icon matListIcon>local_atm</mat-icon>
</a>
<a mat-list-item routerLink="/settings" routerLinkActive="active-list-item">
<h2 matLine>Despesas</h2>
<mat-icon matListIcon>show_chart</mat-icon>
</a>
<a mat-list-item routerLink="/settings" routerLinkActive="active-list-item">
<h2 matLine>Notificações</h2>
<mat-icon matListIcon>notification_important</mat-icon>
</a>
</mat-nav-list>
</mat-sidenav>
<mat-sidenav-content fxFlexFill>
<mat-toolbar>
<button class="hamburger mat-button" mat-icon-button (click)="sidenav.toggle()">
<mat-icon>menu</mat-icon>
<span>Menu</span>
</button>
<span>Bem vindo ao CPF, Pedro</span>
<button mat-icon-button [mat-menu-trigger-for]="menu">
<mat-icon>more_vert</mat-icon>
</button>
</mat-toolbar>
<mat-menu x-position="before" #menu="matMenu">
<button mat-menu-item>
<mat-icon>person</mat-icon>
<span>Perfil</span>
</button>
<button mat-menu-item>
<mat-icon>money_off</mat-icon>
<span>Sair</span>
</button>
</mat-menu>
</mat-sidenav-content>
</mat-sidenav-container>
profile.component.css
mat-toolbar {
background-image: linear-gradient(to bottom, #00b4db, #0083b0);
color: #fff;
justify-content: space-between;
box-shadow: 0 2px 5px 0 rgba(0,0,0,.3);
}
span {
font-size: 16px;
font-weight: 700;
}
.hamburger {
height: 100%;
font-size: 18px;
}
.mat-sidenav-container {
min-width: 400px;
max-width: 100%;
}
.mat-sidenav {
flex: 0 1 auto;
}
.menu-spacer {
flex: 1;
}
.mat-list-item-content {
padding: 0 25px;
}
.menu-container {
min-width: 200px;
max-width: 100%;
}
First, please consider that you need to insert style into the project:
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
If it did not solve the problem, I guess it's because you are using another font and also added !important for the font, please try to add the code below in your style.css (or style.scss) which is the general/public CSS file which effects on your whole project:
mat-icon{
font-family: 'Material Icons' !important;
}
so here is my script code
class Pop extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
let currDoc = document.currentScript.ownerDocument;
let template = currDoc.querySelector('#pop-up');
let tmplNode = document.importNode(template.content, true);
let shadowRoot = this.createShadowRoot();
shadowRoot.appendChild(tmplNode);
shadowRoot.querySelector("#content").innerHTML = this.innerHTML;
}
}
window.customElements.define("pop-up", Pop);
and here is my template
<template id="pop-up">
<style>
* {
padding: 0;
margin: 0;
}
.btn {
//styling
}
.btn:hover {
//styling
}
#box{
//styling
display: none;
}
#box h1{
//styling
}
</style>
<div id="box">
<h1> Your Shopping Cart</h1>
<text id="content"> </text>
<button class="btn" onclick="close()"> Close </button>
</div>
</template>
and in my index file i have this
<button class="btn" onclick="pop()">Show Pop</button>
<pop-up id="pop"> pop-up box</pop-up>
<script>
function pop(){
document.getElementById("pop").style.display = "block";
}
</script>
I am trying to do a pop-up box. And when i click on the "Show Pop" button i want to change the display property from style to "block" from "none". But for some reason it doesn't work. Im new to this shadow dom elemets and i can't really figure it out.
It is hard to explain everything in this answer, but the following code will give you an overview of how the solution might look like.
MDN, as usual, has the perfect intro for web components and shadow doms here
class Pop extends HTMLElement {
constructor() {
// Always call super first in constructor
super();
let template = document.getElementById('pop-up');
let templateContent = template.content;
// Create a shadow root
const shadowRoot = this.attachShadow({ mode: 'open' })
.appendChild(templateContent.cloneNode(true));
// attach close listener
this.shadowRoot.querySelector('.btn').addEventListener('click', this.close.bind(this));
}
// close pop-up
close() {
this.style.display = 'none';
}
// open pop-up
open() {
this.style.display = 'block';
}
}
window.customElements.define("pop-up", Pop);
function pop() {
// notice we are using the open method here
document.getElementById("pop").open();
}
<template id="pop-up">
<style>
:host {
display: none;
}
* {
padding: 0;
margin: 0;
}
.btn {
//styling
}
.btn:hover {
//styling
}
#box {
//styling
display: none;
}
#box h1 {
//styling
}
</style>
<div id="box">
<h1> Your Shopping Cart</h1>
<!-- Notice we are using slots -->
<slot> </slot>
<button class="btn"> Close </button>
</div>
</template>
<button class="btn" onclick="pop()">Show Pop</button>
<pop-up id="pop"> pop-up box </pop-up>