I am trying to get the parent element when a ng-template is added with ngIf.
Layout:
<section>
<h2>
Hello!
<ng-container *ngIf="someCondition1; then myTemplate"></ng-container>
</h2>
<div>
Another one!
<ng-container *ngIf="someCondition2; then myTemplate"></ng-container>
</div>
<!-- More markup here that show myTemplate based on conditions -->
</section>
<ng-template #myTemplate>
<img src="path/to/image.png" />
</ng-template>
The above works fine but I want to grab the parent element of wherever it's added and apply some styles to the parent in the typescript file. Can I use one of the lifecycle functions or select the parent another way whenever the template is added to the DOM?
I figured it out. I had to add a template tag on the image and then grab it with ViewChildren of type QueryList. This gave me access to the native element of each one where I could grab the parent node. This has to to be done in the AfterViewInit lifecycle hook.
HTML:
<section>
<h2>
Hello!
<ng-container *ngIf="someCondition1; then myTemplate"></ng-container>
</h2>
<!-- More markup here that show myTemplate based on conditions -->
</section>
<ng-template #myTemplate>
<img #myTemplateImage src="path/to/image.png" />
</ng-template>
Typescript:
import { Component, ViewChildren, ElementRef, QueryList, AfterViewInit } from '#angular/core';
#Component({
selector: 'test-selector',
templateUrl: './test.component.html'
})
export class TestComponent implements AfterViewInit {
#ViewChildren('myTemplateImage') imageList: QueryList<ElementRef>;
ngAfterViewInit() {
this.imageList.forEach((item) => {
item.nativeElement.parentNode.style.fontStyle = 'italic';
});
}
}
Related
I'm working on a react with nextjs project.
I'm using Link to scroll to a specific section on the same page.
Here is one of the components that use Link:
import styles from './section1.module.scss';
import Image from 'next/image';
import Button from '#material-ui/core/Button';
import tought_process from '../../../public/thought_process.png';
import Link from 'next/link';
const Section1 = () => {
return (
<div className={styles.container}>
<div className={styles.left}>
<div className={styles.leftContainer}>
<Link href='#enews'>
<div className={styles.buttonContainer}>
<Button className={styles.buttonstyle1}>Get started</Button>
</div>
</Link>
</div>
</div>
<div className={styles.right}>
<Image
src={tought_process}
className={styles.imageStyle}
alt='how to think about organizing'
layout='responsive'
priority
/>
</div>
</div>
);
};
export default Section1;
And here i mark the element with the id:
<div {...handlers} className={styles.bigBody}>
<NavBar open={menuOpen} toggle={setMenuOpen} scrollY={scrollY} />
<SideMenu open={menuOpen} toggle={setMenuOpen} scrollY={scrollY} />
<div className={styles.sections}>
<Section1 />
<Section2 />
<Section3 id='enews' />
<Section4 />
</div>
Can't figure out what i'm doing wrong.
Multiple clickable elements are wrapping each other. Remove the button and add the anchor element.
<Link href="#enews">
<a>Get started</a>
</Link>
<Link href="#enews">
<a className={styles.buttonContainer}>
<span className={styles.buttonstyle1}>Get started</span>
</a>
</Link>
I'd recommend updating the styles so you can remove the inner span element.
I use a custom link component that does a few things (not shown); one is smooth scroll to hash routes if the browser supports smooth scrolling (not Safari).
import NextLink, { LinkProps } from "next/link";
import { HTMLProps, MouseEvent, FC } from "react";
export const Link: FC<LinkProps & HTMLProps<HTMLAnchorElement>> = ({ as, children, href, replace, scroll, shallow, passHref, ...rest}) => {
const onClick = (event: MouseEvent<HTMLAnchorElement>) => {
if (href.startsWith("#")) {
event.preventDefault();
const destination = document.getElementById(href.substring(1));
if (destination) destination.scrollIntoView({ behavior: "smooth" });
}
};
return (
<NextLink as={as} href={href} passHref={passHref} replace={replace} scroll={scroll} shallow={shallow}>
<a href={href} {...rest} onClick={onClick}>
{children}
</a>
</NextLink>
);
};
I removed new lines to condense the code block
If you went with the above approach, don't include the anchor tag since it's automatically included.
import { Link } from "./custom/path/link"
<Link href="#enews">Get started</Link>
Two points here:
As per the nextjs, passHref has to be used if a custom element is used as a child of Link tag instead of an anchor tag.
As per the same docs value of href should be '/#enews' not '#enews'
I am trying to add a transition of 2 png into an png in the background, and be able to switch between different scenarios ( like living-room, dining-room etc). Once I click the element that select which area of the house I am choosing, the images gets fetched but because it takes some time to load, the transition doesn't happen and they just appear on top of the image on the background without transition smoothly from the side. I need a way to load the all the images once the route gets rendered, or maybe if you could give me another solution for the problem I'm happy to hear it.
This is the template
<template>
<div>
<div v-for="item in painting" :key="item.id" class="showroom">
<div class="setting">
<h1>{{ item.name }}</h1>
<h3>Choose a room:</h3>
<div v-for=" (room, idx) in rooms" :key="idx" id="roomSelection">
<img id="roomSelector" :src="room.roomview" #click="showRoom(room)">
<p>{{ room.name }}</p>
<br>
</div>
<p>Choose a wall color: <span id="colorPicker" :style="{backgroundColor: color}"><input type="color" id="base" v-model="color"></span></p>
<router-link to="/painting"><i class="fas fa-arrow-left"></i></router-link>
</div>
<div class="display">
<div :style="{backgroundColor: color}" class="wall">
<img :src="item.img">
</div>
<div class="forniture">
<transition name="slideIn">
<div v-for=" room in roomToShow" :key="room" class="setForniture">
<img class="left" :id="room.space1" :src="room.forniture1">
<img class="left" :id="room.space2" :src="room.forniture2">
</div>
</transition>
</div>
</div>
</div>
</div>
</template>
This is the script
<script>
import { mapGetters } from 'vuex'
import sofa from '../assets/sofa.png'
import lamp from '../assets/lamp.png'
import table from '../assets/table.png'
import cabinet from '../assets/cabinet.png'
import chair from '../assets/chair.png'
import sidetable from '../assets/sidetable.png'
import livingroom from '../assets/livingroom.png'
import diningroom from '../assets/diningroom.png'
import lounge from '../assets/lounge.png'
export default {
data(){
return{
color: '#243E36',
rooms: [
{ name: "lounge", space1: 'lounge1', space2: 'lounge2', forniture1: chair, forniture2:sidetable, roomview: lounge},
{ name: "livingroom" , space1: 'livingroom1', space2: 'livingroom2', forniture1:sofa, forniture2:lamp, roomview: livingroom },
{ name: "diningroom" , space1: 'diningroom1', space2: 'diningroom2', forniture1: table, forniture2: cabinet, roomview: diningroom }
],
roomToShow: [],
}
},
computed: {
...mapGetters([
'showPainting',
'forSale',
'painting'
]),
},
methods: {
showRoom(room) {
setTimeout( () => {
this.roomToShow.shift();
this.roomToShow.push(room);
}, 100)
}
}
</script>
The easiest way to solve this issue is to just hide the images with CSS but nevertheless load all from the beginning (render the elements). You can do that by dynamically adding a class to the currently visible image with :class="".
A more complex solution, which gives you also a callback for when the images are loaded, is to create a new Image() for all images and react to onload/onerror. Like this, you can load all the images in the beginning with JS abd without changing the HTML. Here is an example for this use case: How to create a JavaScript callback for knowing when an image is loaded?
I am working with bootstrap 4 and angular 7, I have the following element, which function is to hide/show a sidebar.
<a
class="app-sidebar__toggle"
href="#"
data-toggle="sidebar"
aria-label="Hide Sidebar"
></a>
The problem is when I enter to specific route, this reloads all page. These are my routes in app-routing.module.ts
const routes: Routes = [
{ path: 'home', component: HomeComponent },
{ path: 'catalogo/lista', component: CatalogoListaComponent },
{ path: '', redirectTo: 'home', pathMatch: 'full' }
];
So, if I enter to http://localhost:4200/home, the error will happen, but if I enter to any other route (my default empty route will redirect to /home), for example, http://localhost:4200 or http://localhost:4200/a_route_that_not_exists, I am redirected to /home (as expected), and the button that show/hide the sidebar works well.
I hope to be clear in my problem and I would like a lot you can help me.
EDIT: More code of my application...
This is my app.component.html:
<app-header></app-header>
<app-sidebar></app-sidebar>
<div class="app-content">
<app-title [titulo]="titulo" [icono]="icono [breadcrumbs]="breadcrumbs"></app-title>
<router-outlet></router-outlet>
</div>
This is my header.component.html (where I use the link to show/hide the sidebar):
<header class="app-header">
<a class="app-header__logo" href="index.html">Vali</a>
<!-- Sidebar toggle button-->
<a
class="app-sidebar__toggle"
href="#"
data-toggle="sidebar"
aria-label="Hide Sidebar"
></a>
<p>.... more html</p>
</header>
And this is my sidebar.component.html:
<div class="app-sidebar__overlay" data-toggle="sidebar"></div>
<aside class="app-sidebar">
<div class="app-sidebar__user">
<img
class="app-sidebar__user-avatar"
src="https://s3.amazonaws.com/uifaces/faces/twitter/jsa/48.jpg"
alt="User Image"
/>
<div>
<p class="app-sidebar__user-name">John Doe</p>
<p class="app-sidebar__user-designation">Frontend Developer</p>
</div>
</div>
<ul class="app-menu">
<li>
<a class="app-menu__item" [routerLink]="['/home']">
<i class="app-menu__icon fa fa-home"></i>
<span class="app-menu__label">Inicio</span>
</a>
</li>
more menu elements...
<ul>
</aside>
Generally a lot of the Popper/ JS elements in Bootstrap won't work out of the box in Angular; Angular provides a pretty solid way to handle elements like sidenavs.
Since the element you want to use to toggle the sidenav doesn't exist in the same component as the sidenav, you can set up a basic service that handles the sidenav state. To create your sidenav service (run in console in your project root):
ng g s sidenav
And then in the generated sidenav.service.ts:
import {Injectable} from '#angular/core';
import {BehaviorSubject} from 'rxjs';
#Injectable()
export class SidenavService {
public isOpen: boolean = false;
public toggleChange: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
constructor() {}
public toggle(): void {
this.isOpen = !this.isOpen;
this.toggleChange.next(this.isOpen);
}
}
In your header.component.html you can modify the clickable element that will show/ hide the sidebar:
<a
class="app-sidebar__toggle"
aria-label="Hide Sidebar"
(click)="toggleSidebar()"
></a>
In your header.component.ts file you can then set toggleSidebar() to run the toggle() function in the service you just set up:
import {SidenavService} from "[wherever your service is located]";
#Component({ /*...*/ })
export class HeaderComponent {
constructor(private sidenavService: SidenavService)
toggleSidebar(): void {
this.sidenavService.toggle();
}
}
You can then (in either your app component or sidebar component) set up reacting to the toggle:
//assuming you're in sidebar.component.ts
import {SidenavService} from "[wherever your service is located]";
import {OnInit, OnDestroy} from "#angular/core";
import {Subscription} from "rxjs";
#Component({ /*...*/ })
export class SidebarComponent implement OnInit, OnDestroy {
isOpen: boolean;
sidenavSubscription: Subscription;
constructor(private sidenavService: SidenavService) {}
ngOnInit() {
this.sidenavSubscription = this.sidenavService.toggleChange.subscribe(isOpenChange => {
this.isOpen = isOpenChange;
});
}
ngOnDestroy() {
this.sidenavSubscription.unsubscribe();
}
}
You can then use the isOpen variable in your sidebar component in different ways to control sidebar activity. Examples include using an [ngClass] directive:
<!--in your header component-->
<header [ngClass]={'active': isOpen, 'inactive': !isOpen} >
</header>
Or you can build in something using angular animations to animate the sidebar in and out (using ngIf and the :enter/ :leave transitions).
I have seen this HTML line in an Angular project:
<div id="player" class="player" [class.voted]="booleanvar">
The CSS contains a defintion of .player.voted
I'm not really sure what this part means: [class.voted]="booleanvar"
This is one way of dynamically applying a class to an HTML element in Angular.
If booleanvar equates to true then the css class voted will be applied, so long as its defined correctly in CSS file. If it equates to false, then the class will not be applied.
<div id="player" class="player" [class.voted]="booleanvar">
I hope you help it
[class.voted]="booleanvar" this means the element add a class "voted" when "booleanvar" poperty or a variable value is true.
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
template:`
<div id="player" class="player" [class.voted]="booleanvar">
`
})
export class AppComponent {
booleanvar:boolean = true;
}
when booleanvar = false then the render element like '<div id="player" class="player">'
Otherwise it's render <div id="player" class="player voted">
Similar to Angular2 two-way data binding, I have a parent and a child component. In the parent I change the value that is passed to the child component via property. The property of the child is named percentage.
https://gist.github.com/ideadapt/59c96d01bacbf3222096
I want to bind the property value to a html attribute value. Like: <div style="width: {{percentage}}%">. I didn't find any syntax that worked. So I ended up using a change listener that does some manual DOM update:
this.progressElement = DOM.querySelector(element.nativeElement, '.progress');
DOM.setAttribute(this.progressElement, "style", `width: ${this.percentage}%`);
Is there a more elegant way to accomplish this?
You can use percentage binding for CSS properties: [style.width.%]
import {Component, Input} from 'angular2/core';
#Component({
selector: 'progress-bar',
template: `
<div class="progress-bar">
<div [style.width.%]="value"> {{ value }}% </div>
</div>
`,
})
export class ProgressBar {
#Input() private value: number;
}
Use NgStyle, which works similar to Angular 1. Since alpha-30, NgStyle is available in angular2/directives:
import {NgStyle} from 'angular2/directives';
Then include NgStyle in the directives list, this should work (here are some examples):
#View({
directives: [NgStyle]
template: `
<div class="all">
<div class="progress" [ng-style]="{'width': percentage + '%'}"></div>
<span class="label">{{percentage}}</span>
</div>
`
})
Alternatively and without using NgStyle, this would work well too:
<div class="progress" [style.width]="percentage + '%'"></div>