I'm working on an app in Angular 4. In one view I'm using some svgs placed between data that is retrieved from API request. It looks like this:
<div>
<div>
<svg><use xlink:href="./assets/svg/machine.svg#message-icon"></use></svg>
</div>
<div>{{machine.state}}</div>
<div>
<svg><use xlink:href="./assets/svg/machine.svg#settings-icon"></use></svg>
</div>
</div>
All svgs are put in one file (machine.svg), and are defined in < symbol > tags with ids.
Now my problem is when I set interval every 2 seconds to update data from API request, my svg icons appear to flicker with every update, but it only happens in Chrome.
I've checked the network logs and it seems that whole svg file is downloaded with every API request:
While in Mozilla everything works fine, svg is downloaded only once:
I've tried to put svg in < object > tag, but the requests are even more numerous. Putting every single svg in < img > tag seemed to resolve the problem with requests, but I would prefer to be in control of "fill" property. Putting the whole svg directly on the page solved problem too, but it doesn't seem to be a clean solution.
My question is if there is a way to retrieve svg from a file without Chrome downloading it constantly?
Also faced with this situation, the easiest way for me is to create a separate shared component that contains all the icons I need and which takes the parameters I need. For me, this is the fastest and most flexible solution.
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'ngx-svg-icon',
templateUrl: './svg-icon.component.html',
styleUrls: ['./svg-icon.component.scss']
})
export class SvgIconComponent implements OnInit {
#Input() icon: string;
#Input() fill: string;
constructor() {}
ngOnInit() {}
}
Usage:
<ngx-svg-icon [icon]="'settings'" [fill]="'#ededed'"></ngx-svg-icon>
Try to configure your server to add cache-control headers to those requests. I have the same issue on localhost, but on my production server icons come with cache-control: max-age=172800 header and are not re-requested again.
Related
So I have two components (Preview component and an app component) in an angular project. I displayed one of the components (preview Component) inside the other(app component) through a relative path in an iframe:
app.component.html
<iframe
src="/pagepreview"
width="478"
height="926"
id="iframeId"
#previewFrame
>Alternative text
</iframe>
Now, In the app.component.ts file, I want to access the elements of the preview template, so I did this:
#ViewChild(`previewFrame`,{ static: true }) previewFrame: ElementRef;
onLoad() {
let frameEl: HTMLIFrameElement = this.previewFrame.nativeElement;
let showTemplate = frameEl.innerHTML
console.log(showTemplate )
}
The result I got was the Alternative text word inside the iframe tag.
Does anyone have an idea how I can get to access the HTML tags in the preview component page? I need to do this so I can do some DOM manipulation of the page preview component on the app component page. Thanks.
Apparently the page renders faster than the function that calls the DOM object, despite calling it inside an ngAfterViewInit life cycle hook. I added a setTimeout() function and now I'm able to get the DOM elements I want.
This answer provided the context to my solution.
I will preface this by saying that there are several answers online for this exact same purpose with an Angular app. However, none of these appear to be working for me.
I have so far tried nearly everything from these issues
1 , 2 , 3 , 4 and a couple of other but I am still not getting the correct behavior.
Currently, if I am scrolled down and viewing a route, then I click on the item to view a new route via routerLink, the page stays in the same position. This causes some issues especially when on a smaller screen or when looking down a long list of items.
In the current app, my AppComponent is as follows (I am using Angular Material):
<app-header></app-header>
<mat-sidenav-container class="main-sidenav">
<mat-sidenav class="sidenav"#sidenav [mode]="mode" [opened]="openSidenav">
<app-sidenav></app-sidenav>
</mat-sidenav>
<mat-sidenav-content>
<router-outlet></router-outlet>
</mat-sidenav-content>
</mat-sidenav-container>
The router-outlet is displaying one of the following:
A workout page with a list and filter
A workout "detail page" that shows the workout description
In the workout list, when an item is clicked, it has a routerLink: <a [routerLink]="[i]"> where i is the index of the current workout coming from an array (from a firebase database in my case).
Here is the stackblitz to this code. The workouts-page can be found at src/app/features/workouts-page/ and then the workout-list, workout-filter, and workout-detail showing up accordingly. However, when clicked on the routerLink, the scroll position stays the same on the workout detail page.
Since Angular 6+, the recommended method has been to use, within your routing module:
...
imports: [RouterModule.forRoot(routes,{scrollPositionRestoration: 'top'})
...
At the same time, you could also use "enabled" and get the same result. This does not change any behavior in the application I am running. Furthermore, I have tried a few "work-arounds" such as adding the following to my main component:
export class MyAppComponent implements OnInit {
constructor(private router: Router) { }
ngOnInit() {
this.router.events.subscribe((evt) => {
if (!(evt instanceof NavigationEnd)) {
return;
}
window.scrollTo(0, 0)
});
}
}
This workaround and many others have once again not changed the behavior and it leaves the page in the same scroll position as before the router was clicked. One different method from the GitHub request is to add an (activate)="onActivate($event) to the <router-outlet>, where the function runs window.scrollTo(0, 0);. Still, this doesn't seem to be working. I can even put the window.scrollTo(0,0) or scrollTop() in the ngOnInit of any component related to the router outlet, and you guessed it, still no change.
I figured this had to do with the css of the page altogether so I changed my body and html positioning as mentioned in stack overflow issues with the same end result.
Is there a way that I can "reset" the scroll position when a route is clicked with the routerLink property so the page doesn't stay in the same scrolled position when the route is loaded?
ScrollPositionRestoration works properly with full page scroll (body getting scrolled).
In your case internal divs are scrollable. To restore scroll for those you need to customize the scroll restore process.
Here is a blog link which talks about the same https://www.bennadel.com/blog/3534-restoring-and-resetting-the-scroll-position-using-the-navigationstart-event-in-angular-7-0-4.htm
Base idea is to storage scroll position before navigation and restore them if you visit the page due to pop state.
Nikhil gave a method for the ScrollPositionRestoration and helped me find the reason this wasn't working, but I ended up finding a way to do this with a simple activate function.
Like he mentioned, the reason this wasn't working is due to the "body" being reset on the scroll. Since the fixed header was at the top of the body, it was already in the "top" scroll position all the time. To fix this, I added the following simple code:
In the AppComponent HTML
...
<mat-sidenav-content id="detail">
<router-outlet (activate)="resetPosition();"></router-outlet>
</mat-sidenav-content>
...
Note, the (activate)="resetPosition(); causes the function to run any time a router is activated (or any time you go to a new router). Here is the function added to the app.component.ts:
AppComponent TS
resetPosition() {
let myDiv = document.getElementById("detail");
myDiv.scrollTop = 0;
}
This takes the id="detail" from the html container and then sets the scroll position to the top of this div rather than the entire body which was already at the top.
I created a new Angular 7 app using the Angular CLI. I left the configuration/setup the default and started adding my code on-top of it. The AppComponent makes a service call to fetch some blog posts and pass them to a child component that renders them.
app.component.html
<div class="container">
<header class="header-site">
<p class="site-title">Sully<p>
<p class="site-tagline">Code-monkey</p>
<p class="site-description">A sotware development blog with a touch of tech and a dash of life.</p>
</header>
<div *ngFor='let post of posts'>
<app-blog-post [post]="post"></app-blog-post>
</div>
</div>
<router-outlet></router-outlet>
app.component.ts
#Component({selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css']})
export class AppComponent {
posts: Post[];
constructor(private blogService: BlogService) {}
async ngOnInit() {
await this.loadPosts();
}
async loadPosts() {
this.posts = await this.blogService.getPosts();
}
}
This loads the first 10 posts and renders them with my app-blog-post child component.
blog-post-component.html
<article>
<header>
<h1 class="post-title"><a [routerLink]="['/blog', post.path]">{{post.title}}</a></h1>
<h4 class="post-details">{{post.details}}</h4>
</header>
<div>
<markdown ngPreserveWhitespaces [data]="post.content">
</markdown>
</div>
</article>
blog-post-component.ts
#Component({
selector: 'app-blog-post',
templateUrl: './blog-post.component.html',
styleUrls: ['./blog-post.component.css']
})
export class BlogPostComponent implements OnInit {
#Input() post: Post;
}
What happens is that it renders the first 10 blog posts as expected. Now, in the app-routing.module.ts class, I've added /blog/:id as a route, which my child component routes to when you click the post title.
const routes: Routes = [
{
path: 'blog/:id',
component: BlogPostComponent
}
];
I don't understand the routing mechanics here. When I navigate to the post, via the router, nothing happens. I see the URL path change in the browsers URL bar, but the content doesn't change.
What I'm trying to do is replace the 10 posts in the current view with the single post that's already been fetched as the only post on the page, when I hit that specific route. I've read through the docs but can't tell how to replace the content already in the view, with a subset of that content using the component already created. I'm not sure if I have to move the rendering of 10 posts off to a unique route, and just keep the router-outlet as the only element in the app.component.html, and for the '/' route, route to the component containing the top 10 posts. I'm worried that makes sharing the post data I've already fetched, between sibling components, more difficult as the parent now has to push/pull between the two children. Is that a backwards way of handling it?
Further still, each blog post has a unique route. I'm porting my blog from an existing hosted service where the path to a post is /blog/year/month/day/title. Can my Routes object be as simple as /blog/:year/:month/:day/:title? Since I can't get the routing working, I'm unable to test the routing itself and see if that's doable. I want to keep the same routing so existing bookmarks and search engine result links aren't broken.
When you utilise the routing mechanism, the specified component gets rendered in the appropriate router-outlet. I suspect that what you see when you navigate to a route, is the selected post rendered at the very bottom of the page.
As you allude to, if you would like to render the list of default blog posts, you will need to create a separate route/component for it, letting the router-outlet render it for you.
The way the routing works, is by going through each entry in your routes list and checking the current path for a match. This means the order is important. If you want to track both /blog/:year/:month/:day/:title and blog/:id, you will want to order them most specific first. And if you want an empty URL to point to a landing page, as you have above you could put something like { path: '', pathMatch: 'full', [component/redirectTo] } at the bottom of your route list.
In my VueJs application, I present Pie-Chart & table showing data. On legend onClick I am trying to filter table rows.
Here is my code
<pie-chart
:donut="false"
:data="charInfo"
:responsive="true"
:library="{legend:{display: true,onClick:itemSelected}}"
legend="top"
:colors="['#5C9AFF', '#FF3263']"/>
Here clicking on particular legend, 'itemSelected' get called with 2 arguments, 1st is mouseEvent & 2nd is legendItem. I can get the legend text & can filter table rows. But the problem is, it override default behaviour & striking-out legend & hide/show section of pie chart disabled. While searching, I came across
ChartJs Legend onClick Issue. It says to store original legend onClick event & call that from my code. I can store original onClick using something like 'const original = Chart.defaults.pie.legend.onClick', but problem is I dont have chart object to call 'original.call(,event,legendItem). And here I am sort of stuck.
I went round and round trying a range of different implementations to solve this very problem for myself while using ng2-charts (incidentally it is how i found this question). I ended up adding an #ViewChild to reference my chart that was defined in the html.
references needed in the ts for this workaround:
import { Component, Input, Host, OnInit, OnDestroy, ViewChild, AfterViewInit } from '#angular/core';
import { ChartsModule, BaseChartDirective } from 'ng2-charts';
added to the top of my class in the ts:
export class StatisticsComponent implements OnInit, OnDestroy {
#ViewChild('myChart') myChart : BaseChartDirective;
the html object for the chart using ng2-charts:
<canvas *ngIf = "loaded"
baseChart
#myChart = 'base-chart'
[datasets]="chartData"
[colors]="chartColors"
[chartType]="'line'"
[labels]="chartLabels"
[options]="chartOptions"
[legend]="true"
(chartClick)="onChartClick($event)">
</canvas>
In the method you then use for overriding the legend onClick (I personally did it in the chartOptions) you can add this line of code:
this.chart.data.datasets[legendItem.datasetIndex].hidden = !this.chart.data.datasets[legendItem.datasetIndex].hidden;
this.chart.update();
This replicates the standard "onClick" of the legend Item so you dont need to bother storing the original function. Probably important to mention that in this code the legendItem is the second parameter from the onClick (with the first being the event):
function(e: any, legendItem: any)
The only issue with this fix is that by default, in ng2-charts the .hidden artibute is not exposed by default so i had to do a little hack in the module following this method:
https://github.com/valor-software/ng2-charts/issues/915.
While i appreciate your question does not relate to ng2-charts. I imagine that you could likely use a similar method in your wrapper. At the very least I figured that after I managed to sort my problem that I would share with you what I did :)
I'm using AngularJS and FusionCharts together in my web application. The upcoming release of AngularJS v1.3.0 will require there to be a <base> tag in the HTML head, so as to resolve all relative links, regardless of where the app is hosted in the site's directory.
When including the <base> tag with the latest version of FusionCharts (currently v3.4.0), the 3D charts do not render properly. For example, a 3D pie chart appears and has rotation and clickable slices, as well as tooltips and color on the inside edges of the slices. However, the entire outer color is black.
Excluding the <base> tag causes the chart to look and behave normally.
Does anyone have any idea how to resolve this? Unfortunately, I don't own the source code, otherwise I'd hack at it myself.
Here's the link to the non-working jsFiddle: http://jsfiddle.net/aaronaudic/59Bmf/207
SOLUTION:
I originally awarded the correct solution to #pankaj, because his solution of simply adding the following line at page load seemed to fix the problem:
document.getElementsByTagName("base")[0].setAttribute("href", window.location.pathname+window.location.search);
However, this only works if the charts are on the initially-loaded page (when user navigates directly to page with the charts); when navigating to the page by way of something like ui-router, I was still seeing black.
The correct answer is from #Shamasis. Adding the following to the page load will fix the issue application-wide:
eve.on('raphael.new', function () {
this.raphael._url = this.raphael._g.win.location.href.replace(/#.*?$/, '');
});
His original caveat mentioned that the export functions may be hampered from the Cloud, and this may be (I don't export from the cloud). However, exporting locally, as can be observed in this jsFiddle, works perfectly well: http://jsfiddle.net/aaronaudic/Gs6sN/14
My base tag, in the head of the page, is simply:
<base href="/">
The issue is not primarily with FusionCharts - it is a generic SVG issue. In SVG, when gradient colour is applied on an element, it actually "refers" to another gradient element on the page. This is somewhat similar to how links can refer to hashtags using <a id="hash" /> on a page.
Now, when you are setting a <base> to the page, the references are going haywire. The gradients now refer to an element with URL as provided in your base tag.
One way to fix this is to access the internal graphics renderer and hard-set the URL to the absolute location of the page. The way to do that would be to access RedRaphael object within FusionCharts core and set the _url variable for the same.
The following code should do it for you.
eve.on('raphael.new', function () {
this.raphael._url = this.raphael._g.win.location.href.replace(/#.*?$/, '');
});
The only caveat is that specifying absolute URL as reference to gradient elements does not work in some older browsers (I'm not sure exactly - could be Safari 5 or FF 3.0). It may also have some negative impact while exporting FusionCharts as image - the relative URLS will be invalidated when the SVG is rasterised on FusionCharts Cloud Export servers.
I'm fixing this problem on Angular 7 and FusionCharts here, FusionCharts.options.SVGDefinitionURL='absolute' didn't help much. It fixed Safari, but just on the first route with pie charts. Any further navigation would break again, and now Chrome is also broken after navigating.
I considered the Raphael workaround, but couldn't find a way to get a eve reference, so I decided to try removing the baseHref, which seems to be the "cause" of the problem.
Here is what I did:
Removed the base from index.html (left it there commented so everyone knows why) and also put the favicon as an absolute resource.
<!-- Configuring deployUrl and APP_BASE_HREF in the main module,
see [[this stackoverflow link]] -->
<!--<base href="/">-->
...
<link rel="icon" type="image/x-icon" href="/favicon.ico">
Configured deployUrl on angular.json (see this question)
I'm using multiple projects layout here, so for me it was added on path projects.my-project.architect.build.options.deployUrl, should be simpler in the default project.
"deployUrl": "/",
Configured my app module to inject the configuration for the router (docs)
import { APP_BASE_HREF } from '#angular/common'
...
#NgModule({
...
providers: [
{ provide: APP_BASE_HREF, useValue: '/' },
...
],
...
})
Everything looks good now, all browsers work, Angular router seems to be working fine as well.
TLDR: set FusionCharts.options.SVGDefinitionURL='absolute'; in your JavaScript to fix this problem
Detailed:
There is a FusionChart solution to this.
The problem you mention comes from SVG using relative references to things like gradients, resolving incorrectly due to the tag being present.
This problem is described on https://github.com/angular/angular.js/issues/8934.
One possible solution would be setting $locationProvider.html5Mode({ enabled: true, requireBase: false }); and using absolute paths.
Although this does solve the problem there is a solution by FusionCharts by setting FusionCharts.options.SVGDefinitionURL='absolute'; in your JavaScript. (http://www.fusioncharts.com/dev/api/fusioncharts/fusioncharts-properties.html#FusionCharts.options-attributes-SVGDefinitionURL)
You can simply set absolute url in base tag
document.getElementsByTagName("base")[0].setAttribute("href", window.location.pathname+window.location.search);
Here is jsfiddle: http://jsfiddle.net/59Bmf/208/
In angular you can use bind the url using a property in controller.
$scope.absurl=$location.absUrl()
And use it inside view
<base href="{{absurl}}">
#Shamasis Bhattacharya saved my life.
This solution solved my problem with fusioncharts in Ionic4+Angular8 project.
This is what I did in app.component.ts component:
import { Component } from '#angular/core';
import { Platform } from '#ionic/angular';
declare var eve;
#Component({
selector: 'app-root',
templateUrl: 'app.component.html',
styleUrls: ['app.component.scss']
})
export class AppComponent {
constructor() {
this.initializeApp();
}
initializeApp() {
this.platform.ready().then(() => {
eve.on('raphael.new', function() {
this.raphael._url = this.raphael._g.win.location.href.replace(/#.*?$/, '');
});
});
}
}