How to change this body manipulation with section manipulation in Angular? - javascript

We are trying to change CSS id's based on time. The point is that currently, it manipulates the body. How can we change it into section manipulation?
Angular part
ngOnInit() {
this.run(1000, 10)
}
run(interval, frames) {
var int = 1;
function func() {
document.body.id = "b"+int;
int++;
if(int === frames) { int = 1; }
}
var swap = window.setInterval(func, interval);
}
HTML
<section class='full-screen'>
...
...
</section>
there are different css snippets for #b1, #b2, #b3... since above code changes these ids during each time period. I assume something should be changed here:
document.body.id = "b"+int;
How move that function usage from body into above HTML section?

Add a Template reference variable in your template for the section tag:
<section #section class='full-screen'>
...
...
</section>
Add a #ViewChild decoratored variable in your component's ts file to get this element:
#ViewChild('section', { read: ElementRef }) mySection: ElementRef;
Now you can use it like this in your component's ts file:
ngOnInit() {
this.run(1000, 10)
}
run(interval, frames) {
var int = 1;
function func() {
this.mySection.nativeElement.id = "b"+int;
int++;
if(int === frames) { int = 1; }
}
var swap = window.setInterval(func.bind(this), interval);
}
See this simple DEMO
UPDATE:
Note that you're using function func(), this will cause you a scoping problem with using this as your component object. One way to fix this is by using bind function:
var swap = window.setInterval(func.bind(this), interval);
Updated the demo to show this in action.

document.getElementById("div_top1").setAttribute("id", "div_top2");
You can use this to change section id.

You can do it thanks to angular viewChild feature
in your html:
<div #foo class="myClass"></div>
in your component ts file
import { Component, ViewChild, ElementRef, AfterViewInit } from '#angular/core';
// ....
export MyComponernt implement AfterViewInit {
// ....
#ViewChild('foo') foo: ElementRef;
// ....
ngAfterViewInit(): void {
// this is how you manipulate element id properly thanks to angular viewChild feature
this.foo.nativeElement.id = 'something';
}
// ....
}

Related

Is there an Angular way of doing: document.getElementById(id).style.display = "none";

I have a div with the id of 1. I'm trying to set the display to none dynamically. Is there an Angular way of doing this. Currently, I'm using vanilla javascript. I was asking about doing this dynamically because there will be over 60 divs that will be created from an array.
In my html
<div *ngFor="let item of items; i = index;">
<div id={{i}} (click)=hideDiv()></div>
</div>
In my method
hideDiv() {
return document.getElementById('1').style.display = "none";
}
That works but I'm looking for the Angular way of doing the above.
It was suggested that I use #ViewChild. Here's what I've changed. I can't use a Template Reference Variable as the html divs are created dynamically. Unless someone can let me know how to create the template variables dynamically. Although I don't think it's possible to create template variables with a loop.
#ViewChild('imgId', { static: true }) elementRef: ElementRef<HTMLDivElement>;
imgId: string;
Then in the method I have:
this.imgId = event.path[0].attributes[1].value;
this.elementRef.nativeElement.style.display = "none";
The event.path[0].attributes[1].value gets me the id of the image. The imgId shows when I console log it. It's still not changing the display on the div to none. Also I'm getting the error:
Cannot read properties of undefined (reading 'nativeElement')
Yes, you can use the ViewChild query in Angular to do this. In your component, define a query like this:
#ViewChild('#1') elementRef: ElementRef<HTMLDivElement>;
Implement the AfterViewInit interface in your component, and inside it, use this:
this.elementRef.nativeElement.style.display = "none";
You can simply use ngIf for this
Component
shouldDisplay: boolean = true;
hide(): void {
this.shouldDisplay = false;
}
show(): void {
this.shouldDisplay = true;
}
Html
<button (click)="hide()">Hide</button>
<button (click)="show()">Show</button>
<div *ngIf="shouldDisplay">this is the content</div>
Here is the working example
This is the Angular way:
template
<div *ngIf="showMe"></div>
or
<div [hidden]="!showMe"></div>
TypeScript:
showMe: boolean;
hideDiv() {
this.showMe = false;
}
For dynamic items where your don't know how many you will get the best approach would be to add a directive that would store and adjust that for you:
#Directive({ selector: '[hide-me]' })
export class HideDirective {
#Input() id!: string;
#HostBinding('style.display')
shouldShow: string = '';
}
then in your component just address them by ID:
#Component({
selector: 'my-app',
styleUrls: ['./app.component.css'],
template: `
<div *ngFor="let item of items; let index = index;">
<div hide-me id="{{index}}" (click)="hideDiv(index)">Some value</div>
</div>
`,
})
export class AppComponent {
#ViewChildren(HideDirective) hideDirectives!: QueryList<HideDirective>;
items = [null, null, null];
hideDiv(id: number) {
this.hideDirectives.find((p) => p.id === id.toString()).shouldShow = 'none';
}
}
Stackblitz: https://stackblitz.com/edit/angular-ivy-pnrdhv?file=src/app/app.component.ts
An angular official example: https://stackblitz.com/edit/angular-ivy-pnrdhv?file=src/app/app.component.ts
How about passing the div reference to the hideDiv method directly in the Dom using a template variable like this.
<div *ngFor="let item of items; i = index;">
<div #divElement (click)=hideDiv(divElement)></div>
And in your hide div method you will have access to the element directly
hideDiv(div) { div.style.display = "none";}
Here is a Stackblitz example
https://stackblitz.com/edit/angular-ivy-w1s3jl
There are many ways to do this, but in my opinion this is a simple solution the achieves your goal with less code.
PS:
It is always recommended to use the angular Renderer2 to manipulate Dom elements. This service has the method setStyle which you can use for your code.

How to measure element width of an HTML element in Angular?

I want to dynamically create an element and then get its clientWidth. The code snippet looks like this using the regular DOM API.
HTML Snippet:
<div id="non-existent-element"></div>
The element has its css property visibility set to 'hidden'.
Typescript/ Javascript snippet
let labelContainer = document.querySelector('div#non-existent-element');
labelContainer.innerHTML = `
<span>${fundName}</span>
<span> (${performance}%)</span>
`;
let children = labelContainer.children as HTMLCollectionOf<HTMLElement>
for(let i = 0; i < children.length; i++) {
children[i].style.fontSize = fontSize + + 1 +'px';
}
return labelContainer.clientWidth;
How can I achieve the goal using Angular's Element Ref and Renderer2 API?
Simple usage of clientWidth
app.component.html
<p #test>test is the elementRef name</p>
app.component.ts
export class AppComponent implements AfterViewInit {
#ViewChild('test') test: ElementRef;
constructor(private renderer: Renderer2) {
//ERROR TypeError: Cannot read property 'nativeElement' of undefined
// console.log(this.test.nativeElement.clientWidth);
}
ngOnInit() {
//logs: 583
console.log(this.test.nativeElement.clientWidth);
}
ngAfterViewInit() {
this.renderer.setStyle(this.test.nativeElement, 'backgroundColor', 'red');
this.renderer.setStyle(this.test.nativeElement, 'color', 'white');
this.renderer.setStyle(this.test.nativeElement, 'width', '500px');
//logs: 500
console.log(this.test.nativeElement.clientWidth);
}
}

Better way of manipulating the DOM in Angular2

What's the better way of manipulating the DOM to change the background of a specific div, rather than using document.getElementById('id').style.backgroundImage.
I'm trying to change backgrounds as I change my Url, but the only way I could think and easy is using document.getElementById()
changeBg() {
var urlPath = window.location.pathname.split('/');
switch (urlPath[4]) {
case "Refreshments%20North":
document.getElementById('homeBg').style.backgroundImage = "url('./assets/imgs/spur-2.jpg')";
break;
... more cases
default:
document.getElementById('homeBg').style.backgroundImage = "url('./assets/imgs/background.jpg')";
}
}
I also tried Renderer dependency but how do I target homeBg using this?
this.renderer.setElementStyle(this.elRef.nativeElement, 'background-image', "url(./assets/imgs/spur-2.jpg)");
Template -- is basically just a div
<nav></nav>
<div id="homeBg"></div>
Edit --
Moved my changeBg() to my sharedService
public changeBg() {
var urlPath = window.location.pathname.split('/');
switch (urlPath[4]) {
case "Refreshments%20North":
this.homeBg = this.sanitizer.bypassSecurityTrustStyle("url('./assets/imgs/spur-2.jpg')");
break;
default:
this.homeBg = this.sanitizer.bypassSecurityTrustStyle("url('./assets/imgs/background.jpg')");
}
}
Calling changeBg() service in my profile component
ngOnInit() {
this.sharedService.changeBg(); // is this correct?
}
Profile template -- like this gives me an error Cannot read property 'homeBg' of undefined
<div class="home" id="homeBg" [style.background-image]="changeBg?.homeBg"></div>
Change background with route.param.subscribe()
this.routeSub = this.route.params.subscribe(params => {
this.sharedService.changeBg();
}
Using binding and directives is the preferred way in Angular2 instead of imperative DOM manipulation:
<div [style.background-image]="myService.homeBg"
You need to sanitize the URL for Angular to accept it.
See In RC.1 some styles can't be added using binding syntax for more details.
changeBg() {
var urlPath = window.location.pathname.split('/');
switch (urlPath[4]) {
case "Refreshments%20North":
this.homeBg = this.sanitizer.bypassSecurityTrustStyle("url('./assets/imgs/spur-2.jpg')");
break;
... more cases
default:
this.homeBg = this.sanitizer.bypassSecurityTrustStyle( "url('./assets/imgs/background.jpg')");
}
}
See also How to add background-image using ngStyle (angular2)?
You can use template references and #ViewChild decorator:
template :
<div #myDiv id="homeBg"></div>
component :
class MyComponent implements AfterViewInit{
#ViewChild("myDiv")
elRef:ElementRef
ngAfterViewInit(){
this.renderer.setElementStyle(this.elRef.nativeElement, 'background-image', "url(./assets/imgs/spur-2.jpg)");
}
}

Angular 2 route param changes, but component doesn't reload

So, basically I need to reload my component after id of url parameter was changed. This is my player.component.ts:
import {Component, OnInit, AfterViewInit} from '#angular/core';
import {ActivatedRoute, Router} from '#angular/router';
declare var jQuery: any;
#Component({
selector: 'video-player',
templateUrl: './player.component.html',
styleUrls: ['./player.component.less']
})
export class VideoPlayerComponent implements OnInit, AfterViewInit {
playerTop: number;
currentVideoId: number;
constructor(
private _route: ActivatedRoute,
private _router: Router
) {
}
ngOnInit(): void {
this._route.params.subscribe(params => {
this.currentVideoId = +params['id'];
console.log( this.currentVideoId );
this._router.navigate(['/video', this.currentVideoId]);
});
}
ngAfterViewInit(): void {
if (this.videoPageParams(this.currentVideoId)) {
console.log( "afterViewInit" );
let params = this.videoPageParams(this.currentVideoId);
let fakeVideoItemsCount = Math.floor(params.containerWidth / params.videoItemWidth);
this.insertFakeVideoItems( this.currentVideoId, fakeVideoItemsCount);
this.changePlayerPosition( params.videoItemTop );
}
}
videoPageParams( id ): any {
let videoItemTop = jQuery(`.videoItem[data-id="${id}"]`).position().top;
let videoItemWidth = jQuery('.videoItem').width();
let containerWidth = jQuery('.listWrapper').width();
return {
videoItemTop,
videoItemWidth,
containerWidth
};
}
changePlayerPosition( videoItemTop ): void {
this.playerTop = videoItemTop;
}
insertFakeVideoItems( id, fakeVideoItemsCount ): void {
let fakeVideoItemHTML = `<div class="videoItem fake"></div>`;
let html5playerHeight = jQuery('#html5player').height();
let videoItemIndex = jQuery(`.videoItem[data-id="${id}"]`).index() + 1;
let videoItemInsertAfterIndex;
let videoItemRow = Math.ceil(videoItemIndex / fakeVideoItemsCount);
let videoItemRowBefore = videoItemRow - 1;
if ( videoItemIndex <= 4 ) {
videoItemInsertAfterIndex = 0;
} else {
videoItemInsertAfterIndex = (videoItemRowBefore * fakeVideoItemsCount);
}
let videoItemInsertAfter = jQuery('.videoItem').eq(videoItemInsertAfterIndex);
for ( let i = 0; i < fakeVideoItemsCount; i++ ) {
$(fakeVideoItemHTML).insertBefore(videoItemInsertAfter);
}
jQuery(`.videoItem.fake`).css('height', html5playerHeight);
}
}
player.component.html:
<video
class="video"
preload="auto"
[attr.data-id]="currentVideoId"
src="">
</video>
<videos-list></videos-list>
videoList.component.html
<div class="videoItem" *ngFor="let video of videos" [attr.data-id]="video.id">
<a [routerLink]="['/video', video.id]">
<img [src]='video.thumbnail' alt="1">
</a>
</div>
So when I click <a [routerLink]="['/video', video.id]"> in videoList.component.html it changes route to /video/10 for example, but the part from player.component.ts which manipulates the DOM doesn't fire again - DOM manipulation doesn't update.
I tried to manually navigate to route via this._router.navigate(['/video', this.currentVideoId]); but somehow it doesn't work.
QUESTION
Is there any way to run functions that manipulate DOM each time route param changes in the same URL?
DOM will not update because ngOnInit is only fired once, so it will not update even if you try to "renavigate" back to the parent from the child, since the parent haven't been removed from the DOM at any point.
One option to solve this, is that you could use a Subject, that when the routing is happening, let's send the chosen video id to parent, which subscribes to the change and does whatever you tell it to do, meaning calling functions that will update the DOM, so probably what you want re-executed is the inside ngOnInit and ngAfterViewInit
You mentioned that you had tried using
this._router.navigate(['/video', this.currentVideoId])
so let's look at that. Probably have some click event that fires a function. Let's say it looks like the following, we'll just add the subject in the play
navigate(id) {
VideoPlayerComponent.doUpdate.next(id)
this._router.navigate(['/video', this.currentVideoId])
}
Let's declare the Subject in your parent, and subscribe to the changes:
public static doUpdate: Subject<any> = new Subject();
and in the constructor let's subscribe to the changes...
constructor(...) {
VideoPlayerComponent.doUpdate.subscribe(res => {
console.log(res) // you have your id here
// re-fire whatever functions you need to update the DOM
});
}

Create custom script for DOM Manipulation

I'm currently working on an Angular 2 Project where I have a menu that should be closable by a click on a button. Since this is not heavy at all, I would like to put it outside of Angular (without using a component for the menu).
But I'm not sure of how to do it, actually I've just put a simple javascript in my html header, but shouldn't I put it somewhere else?
Also, what the code should be? Using class, export something? Currently this is my code:
var toggleMenuButton = document.getElementById('open-close-sidebar');
var contentHolder = document.getElementById('main-content');
var menuHolder = document.getElementById('sidebar');
var menuIsVisible = true;
var updateVisibility = function() {
contentHolder.className = menuIsVisible ? "minimised" : "extended";
menuHolder.className = menuIsVisible ? "open" : "closed";
}
toggleMenuButton.addEventListener('click', function() {
menuIsVisible = !menuIsVisible;
updateVisibility();
});
Finally moved to something with MenuComponent and a service, but I'm still encountering an issue.
MenuService.ts
#Injectable()
export class MenuService {
isAvailable: boolean = true;
isOpen: boolean = true;
mainClass: string = "minimised";
sidebarClass: string = "open";
updateClassName() {
this.mainClass = this.isOpen ? "minimised" : "extended";
this.sidebarClass = this.isOpen ? "open" : "closed";
}
toggleMenu(newState: boolean = !this.isOpen) {
this.isOpen = newState;
this.updateClassName();
}
}
MenuComponent.ts
export class MenuComponent {
constructor(private _menuService: MenuService) { }
public isAvailable: boolean = this._menuService.isAvailable;
public sidebarClass: string = this._menuService.sidebarClass;
toggleMenu() {
this._menuService.toggleMenu();
}
}
MenuComponent.html
<div id="sidebar" [class]="sidebarClass" *ngIf="isAvailable">
...
<div id="open-close-sidebar"><a (click)="toggleMenu()"></a></div>
The action are rightly triggered, if I debug the value with console.log, the class name are right but it didn't change the value of the class. I thought the binding was automatic. And I still do not really understand how to change it. Do I have to use Emmit like AMagyar suggested?
The advantage of using angular2 above your own implementation, greatly outweigh the marginal benefit in performance you will get from using plane JavaSccript. I suggest not going on this path.
If you however do want to continue with this, you should export a function and import and call this function inside the ngAfterViewInit of your AppComponent. The exported function should add the click EventListener and (important) set the document.getElementById variables. Because your script possibly won't be able to find those elements yet when it's loaded.
But let me emphasise once more, that angular2 is optimised for exactly these tasks, and once you get more familiar with it, it will also be a lot easier to code it.
update
For inter component communication you should immediately think about a service. Just create a service which stores the menu state and add this to your global ngModule providers array. For instance:
export class MenuService {
public get menuOpen(): boolean {
return this._menuOpen;
}
private _menuOpen: boolean;
public openMenu() : void {
this._menuOpen = true;
}
public closeMenu() : void {
this._menuOpen = false;
}
public toggleMenu() : void {
this._menuOpen = !this._menuOpen;
}
}
You can then inject this service into your menu component and bind the classes open/closed and minimized/extended to the MenuService.menuOpen.
#Component({
selector : 'menu'
template : `
<button (click)="menuService.toggleMenu()">click</button>
<div id="open-close-sidebar" [class.open]="menuService.menuOpen"></div>
`
})
export class MenuComponent {
constructor(public menuService: MenuService){}
}
For other component you can use the same logic to see if the menu is open or closed
update #2
You have to use a getter to get the value from menuService. There is only one way binding:
export class MenuComponent {
constructor(private _menuService: MenuService) { }
public get isAvailable(): boolean {
return this._menuService.isAvailable;
}
public get sidebarClass(): string {
return this._menuService.sidebarClass;
}
toggleMenu() {
this._menuService.toggleMenu();
}
}
FYI, it's better practice to use [class.open] instead of a string class name. If you want to do it like that, it will only require minimal change in your current css.
The main reason of why I want to avoid using Angular component is the
fact that my manipulation should be done over all the website and not
just the "menu" component.
You can create many components in Angular 2, it's easy and very practical.
The action will change the class on my menu (located in my menu
component) and on my main content (located outside of the component).
I don't know how to do it, and I'm not sure that this is the best
way... Maybe by binding the service value directly... –
The main content can have a child that is the Menu itself.
Take a look in this link. There are many solutions, one of them is to "emit" the child changes to the parent.
If you need an example I can provide one quickly.

Categories

Resources