Vue/Vuex watcher dynamic/async component loading - asynchronous

I have a base component within which I have a dynamic component with a v-for that displays based on a computed property.
All I've really tried doing thus far, which was an incorrect methodology, was to wrap the method that loads data in a settimeout. This question is as much a methodology question as it is a coding question.
My base component looks like this:
<template>
<div>
<v-progress-linear
v-model="progressValue"
v-if="loading"
></v-progress-linear>
<component
v-for="table in tables"
:key="table.id"
:is="table.structure"
:table="table"
></component>
</div>
</template>
<script>
import Annual from './DataTables/Annual';
import { mapState, mapGetters } from 'vuex';
export default {
name: "Page",
props: [],
components: {
Annual,
},
data: () => ({
progressValue: 0,
loading: false,
tables: [],
}),
computed: {
...mapGetters({
currentTables: 'getCurrentPageTables',
tableTitles: 'getCurrentPageTableTitles',
}),
...mapState({
pageName: state => state.pageName,
snakeName: state => state.snakeName,
}),
methods: {
updateTables(payload) {
this.loading = true;
payload.forEach(title => {
this.tables.push(this.currentTables.filter(e => title === e.name)[0]);
this.progressValue = this.tables.length / payload.length;
})
},
},
watch: {
snakeName: {
handler() {
this.progressValue = 0;
this.updateTables(this.tableTitles);
this.$nextTick(() => {this.loading = false;})
},
immediate: true,
},
}
}
</script>
Annual.vue is simply a component that displays a Vuetify v-data-table element and its structure is fairly inconsequential to this.
For all intents and purposes we can consider currentTables and tableTitles to both be arrays, the first of objects whose data populate the v-data-tables in Annual.vue, and the second of strings which are just the names of the tables.
When the user navigates to another page the getters return different data, based on the page the user navigates to, but some of the pages have over 20 tables, which makes page loading slow upon navigation to these pages. I am trying to do one of two things:
1. Asynchronously load the components one at a time while still making the page functional for the user to navigate through.
2. Display a loader that disappears after all of the content is rendered. I'm having trouble figuring out how to do the latter because I can't put this functionality into the mounted() hook since all of this happens upon the watched parameter changing (hence the component is not re-mounted each time the route changes).
Any advice on how to tackle this would be appreciated.

Related

Using RTK Query to accomplish a classic Load-More page, I don't know what is the right way to do it?

API:
import {
createApi,
fetchBaseQuery,
} from '#reduxjs/toolkit/query/react'
import { RootState } from 'store'
export interface FeedType {
id: string
title: string
imgUrl: string
}
export const feedsApi = createApi({
reducerPath: 'feeds',
tagTypes: ['Feeds'],
baseQuery: fetchBaseQuery({
baseUrl: 'http://localhost:5000',
}),
endpoints: (build) => ({
getFeedsMore: build.query<FeedType[], void>({
async queryFn(arg, queryApi, extraOptions, baseQuery) {
const state = queryApi.getState() as RootState
const selector = feedsApi.endpoints.getFeedsMore.select() as (
state: any
) => any
const result = selector(state) as { data: FeedType[] } | undefined
const oldData = (result?.data ?? []) as FeedType[]
const { data } = await baseQuery({
url: 'feeds?_page=' + Math.round(oldData.length / 10 + 1),
})
return { data: [...oldData, ...(data as FeedType[])] }
},
}),
}),
})
export const {
useGetFeedsMoreQuery,
} = feedsApi
Component:
import FeedItem from 'components/FeedItem'
import React from 'react'
import Masonry from 'react-masonry-css'
import { useGetFeedsMoreQuery } from 'services/feeds'
interface FeedsMorePageProps {}
const FeedsMorePage: React.FunctionComponent<FeedsMorePageProps> = () => {
const { isLoading, data: feeds, refetch } = useGetFeedsMoreQuery()
return (
<>
{isLoading ? (
'loading'
) : (
<>
<Masonry
breakpointCols={{
default: 3,
1100: 2,
700: 1,
}}
className="my-masonry-grid"
columnClassName="my-masonry-grid_column"
>
{feeds &&
feeds.map((feed) => <FeedItem key={feed.id} feed={feed} />)}
</Masonry>
<button className="btn btn-info" onClick={() => refetch()}>
Load More...
</button>
</>
)}
</>
)
}
export default FeedsMorePage
I know it is totally a mess, this is the only clumsy way I could make it run.
What is the best practice to this scenario?
It is common to use dispatch and getState in a Thunk-Action, but the most confusing part to me is in queryFn I have getState and endpoint.select methods, but I do not know how to type them in typescript.
feedsApi.endpoints.getFeedsMore.select()(state as RootState)
// this gives me a super long type incompatible complain
I can not use useSelector hook here neither, util I made out this ugly way...
Generally, no, that's not what you should do. Building one cache entry that large will mean that eventually you will run out of memory. It can never be collected, so it will just keep growing. But if the user scrolled down to item 9500, you really don't need to keep item 1000 in memory any more.
Especially when you are displaying all those elements in your DOM.
And if you are not displaying all those elements in the DOM, there is also no need to have all of them in the cache.
So, assume you use some kind of virtualization library like react-virtual.
That means you know you have theoretically 10000 items to display, but you only render what is in view and a bit to the front and a bit to the back.
Then keep your endpoint also to a window.
Have your endpoint fetch "parts", so if the user is looking at item 9500, you maybe have items 9500-9550 on the screen and you want to keep some more fetched to quickly display them - one page to the front and one to the back.
So now you use your query hook three times in your component: the current "page" (9500-9550), the last "page" (9450-9500) and the next "page" (9550-9600).
That way, stuff not in view can at some point be cache-collected if it was not in view long enough.
Another way of doing that would be to just render "page" components from a parent component - and each of those "page" components would request their "window of data", while a "get more" button would add another element to the "pages" array in the parent component.
But either way: you would not stitch all of that together in the cache, but keep it as separate cache entries - and then decide to access which of those to access in your component and how to stitch them together.
Generally, I can recommend to read up on this GitHub discussion where multiple people share their approaches to the topic.

Hide Docs tab in Storybook

I want to create stories using both Typescript and MDX, therefore I have in my main.js:
module.exports = {
stories: ['../src/**/*.stories.(mdx|ts)'],
addons: ['#storybook/addon-docs', 'storybook-addon-preview']
};
However I don't want to have "Docs" tab next to "Canvas". How do I remove it? Without '#storybook/addon-docs' MDX story is not displayed.
Put this in preview.js:
export const parameters = {
previewTabs: {
'storybook/docs/panel': {
hidden: true
}
}
};
Used in Storybook version 6.0.x
I am currently using #storybook/angular#6.0.21 and the previous answer unfortunately did not work for me. I was able to find a solution in the storybook DocsPage documentation.
The relevant section:
You can replace DocsPage at any level by overriding the docs.page parameter:
- With null to remove docs
- With MDX docs
- With a custom React component
I was able to completely remove the DocsPage for a single story like this:
export const myStory = () => ({
moduleMetadata: MODULE_METADATA,
component: MyComponent,
});
myStory.parameters = {
docs: { page: null },
};

EventRender renders with wrong position

I'm currently building a calendar with the timeline view to get a list of events per teacher. And I want to have a week view of the timeline without showing any time per day. Basically all event of each teacher of that specific day listed on top of each other. Which works if I don't have any custom rendering. It would look like this:
Without eventRender
But, I would like to have a Popover on hover of each event to show more information so I use custom event render to inject Ant Design Popover. And since I'm using react I use ReactDOM to render my custom event.
My code somewhat looks like:
const EventDetail = ({ event, el }) => {
const content = <div>{event.title}<div>{event.description}</div></div>;
ReactDOM.render(
<Popover content={content}>
{event.title}
</Popover>,
el);
};
<FullCalendar
{...someOtherProps}
views={{
customWeek: {
type: 'resourceTimeline',
duration: { weeks: 1 },
slotDuration: { days: 1 },
},
}}
eventRender={EventDetail} />
But for some reason, the positioning of the event somehow got messed up due to improper top position. Also, the height of the column itself isn't tall enough for the amount of events rendered. Which looks like this:
With eventRender
My question is: How can I manage to render the custom events properly? Or how can I wrap my around the event element?
Update: Added codesandbox https://codesandbox.io/s/adoring-field-f1vrj
Thanks!
eventRender={info => {
info.el.id = `event-${info.event.id}`;
const content = <div>Description of {info.event.title}</div>;
setTimeout(() => {
ReactDOM.render(
<Popover content={content}>{info.event.title}</Popover>,
document.getElementById(`event-${info.event.id}`)
);
});
return info.el;
}}
Codesandbox: https://codesandbox.io/s/adoring-field-f1vrj

What is the best way to declare global variables in Vue.js?

I need access to my hostname variable in every component.
Is it a good idea to put it inside data?
Am I right in understanding that if I do so, I will able to call it everywhere with this.hostname?
As you need access to your hostname variable in every component, and to change it to localhost while in development mode, or to production hostname when in production mode, you can define this variable in the prototype.
Like this:
Vue.prototype.$hostname = 'http://localhost:3000'
And $hostname will be available in all Vue instances:
new Vue({
beforeCreate: function () {
console.log(this.$hostname)
}
})
In my case, to automatically change from development to production, I've defined the $hostname prototype according to a Vue production tip variable in the file where I instantiated the Vue.
Like this:
Vue.config.productionTip = false
Vue.prototype.$hostname = (Vue.config.productionTip) ? 'https://hostname' : 'http://localhost:3000'
An example can be found in the docs:
Documentation on Adding Instance Properties
More about production tip config can be found here:
Vue documentation for production tip
a vue3 replacement of this answer:
// Vue3
const app = Vue.createApp({})
app.config.globalProperties.$hostname = 'http://localhost:3000'
app.component('a-child-component', {
mounted() {
console.log(this.$hostname) // 'http://localhost:3000'
}
})
Warning: The following answer is using Vue 1.x. The twoWay data mutation is removed from Vue 2.x (fortunately!).
In case of "global" variables—that are attached to the global object, which is the window object in web browsers—the most reliable way to declare the variable is to set it on the global object explicitly:
window.hostname = 'foo';
However form Vue's hierarchy perspective (the root view Model and nested components) the data can be passed downwards (and can be mutated upwards if twoWay binding is specified).
For instance if the root viewModel has a hostname data, the value can be bound to a nested component with v-bind directive as v-bind:hostname="hostname" or in short :hostname="hostname".
And within the component the bound value can be accessed through component's props property.
Eventually the data will be proxied to this.hostname and can be used inside the current Vue instance if needed.
var theGrandChild = Vue.extend({
template: '<h3>The nested component has also a "{{foo}}" and a "{{bar}}"</h3>',
props: ['foo', 'bar']
});
var theChild = Vue.extend({
template: '<h2>My awesome component has a "{{foo}}"</h2> \
<the-grandchild :foo="foo" :bar="bar"></the-grandchild>',
props: ['foo'],
data: function() {
return {
bar: 'bar'
};
},
components: {
'the-grandchild': theGrandChild
}
});
// the root view model
new Vue({
el: 'body',
data: {
foo: 'foo'
},
components: {
'the-child': theChild
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.16/vue.js"></script>
<h1>The root view model has a "{{foo}}"</h1>
<the-child :foo="foo"></the-child>
In cases that we need to mutate the parent's data upwards, we can add a .sync modifier to our binding declaration like :foo.sync="foo" and specify that the given 'props' is supposed to be a twoWay bound data.
Hence by mutating the data in a component, the parent's data would be changed respectively.
For instance:
var theGrandChild = Vue.extend({
template: '<h3>The nested component has also a "{{foo}}" and a "{{bar}}"</h3> \
<input v-model="foo" type="text">',
props: {
'foo': {
twoWay: true
},
'bar': {}
}
});
var theChild = Vue.extend({
template: '<h2>My awesome component has a "{{foo}}"</h2> \
<the-grandchild :foo.sync="foo" :bar="bar"></the-grandchild>',
props: {
'foo': {
twoWay: true
}
},
data: function() {
return { bar: 'bar' };
},
components: {
'the-grandchild': theGrandChild
}
});
// the root view model
new Vue({
el: 'body',
data: {
foo: 'foo'
},
components: {
'the-child': theChild
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.16/vue.js"></script>
<h1>The root view model has a "{{foo}}"</h1>
<the-child :foo.sync="foo"></the-child>
I strongly recommend taking a look at Vuex, it is made for globally accessible data in Vue.
If you only need a few base variables that will never be modified, I would use ES6 imports:
// config.js
export default {
hostname: 'myhostname'
}
// .vue file
import config from 'config.js'
console.log(config.hostname)
You could also import a json file in the same way, which can be edited by people without code knowledge or imported into SASS.
For any Single File Component users, here is how I set up global variable(s)
Assuming you are using Vue-Cli's webpack template
Declare your variable(s) in somewhere variable.js
const shallWeUseVuex = false;
Export it in variable.js
module.exports = { shallWeUseVuex : shallWeUseVuex };
Require and assign it in your vue file
export default {
data() {
return {
shallWeUseVuex: require('../../variable.js')
};
}
}
Ref: https://v2.vuejs.org/v2/guide/state-management.html#Simple-State-Management-from-Scratch
In vue cli-3 You can define the variable in main.js like
window.basurl="http://localhost:8000/";
And you can also access this variable in any component by using
the the
window.basurl
A possibility is to declare the variable at the index.html because it is really global. It can be done adding a javascript method to return the value of the variable, and it will be READ ONLY.
An example of this solution can be found at this answer:
https://stackoverflow.com/a/62485644/1178478

How to include view/partial specific styling in AngularJS?

What is the proper/accepted way to use separate stylesheets for the various views my application uses?
Currently I'm placing a link element in the view/partial's html at the top but I've been told this is bad practice even though all modern browsers support it but I can see why it's frowned upon.
The other possibility is placing the separate stylesheets in my index.html's head but I would like it to only load the stylesheet if its view is being loaded in the name of performance.
Is this bad practice since styling won't take effect until after the css is loaded form the server, leading to a quick flash of unformatted content in a slow browser? I have yet to witness this although I'm testing it locally.
Is there a way to load the CSS through the object passed to Angular's $routeProvider.when?
I know this question is old now, but after doing a ton of research on various solutions to this problem, I think I may have come up with a better solution.
UPDATE 1: Since posting this answer, I have added all of this code to a simple service that I have posted to GitHub. The repo is located here. Feel free to check it out for more info.
UPDATE 2: This answer is great if all you need is a lightweight solution for pulling in stylesheets for your routes. If you want a more complete solution for managing on-demand stylesheets throughout your application, you may want to checkout Door3's AngularCSS project. It provides much more fine-grained functionality.
In case anyone in the future is interested, here's what I came up with:
1. Create a custom directive for the <head> element:
app.directive('head', ['$rootScope','$compile',
function($rootScope, $compile){
return {
restrict: 'E',
link: function(scope, elem){
var html = '<link rel="stylesheet" ng-repeat="(routeCtrl, cssUrl) in routeStyles" ng-href="{{cssUrl}}" />';
elem.append($compile(html)(scope));
scope.routeStyles = {};
$rootScope.$on('$routeChangeStart', function (e, next, current) {
if(current && current.$$route && current.$$route.css){
if(!angular.isArray(current.$$route.css)){
current.$$route.css = [current.$$route.css];
}
angular.forEach(current.$$route.css, function(sheet){
delete scope.routeStyles[sheet];
});
}
if(next && next.$$route && next.$$route.css){
if(!angular.isArray(next.$$route.css)){
next.$$route.css = [next.$$route.css];
}
angular.forEach(next.$$route.css, function(sheet){
scope.routeStyles[sheet] = sheet;
});
}
});
}
};
}
]);
This directive does the following things:
It compiles (using $compile) an html string that creates a set of <link /> tags for every item in the scope.routeStyles object using ng-repeat and ng-href.
It appends that compiled set of <link /> elements to the <head> tag.
It then uses the $rootScope to listen for '$routeChangeStart' events. For every '$routeChangeStart' event, it grabs the "current" $$route object (the route that the user is about to leave) and removes its partial-specific css file(s) from the <head> tag. It also grabs the "next" $$route object (the route that the user is about to go to) and adds any of its partial-specific css file(s) to the <head> tag.
And the ng-repeat part of the compiled <link /> tag handles all of the adding and removing of the page-specific stylesheets based on what gets added to or removed from the scope.routeStyles object.
Note: this requires that your ng-app attribute is on the <html> element, not on <body> or anything inside of <html>.
2. Specify which stylesheets belong to which routes using the $routeProvider:
app.config(['$routeProvider', function($routeProvider){
$routeProvider
.when('/some/route/1', {
templateUrl: 'partials/partial1.html',
controller: 'Partial1Ctrl',
css: 'css/partial1.css'
})
.when('/some/route/2', {
templateUrl: 'partials/partial2.html',
controller: 'Partial2Ctrl'
})
.when('/some/route/3', {
templateUrl: 'partials/partial3.html',
controller: 'Partial3Ctrl',
css: ['css/partial3_1.css','css/partial3_2.css']
})
}]);
This config adds a custom css property to the object that is used to setup each page's route. That object gets passed to each '$routeChangeStart' event as .$$route. So when listening to the '$routeChangeStart' event, we can grab the css property that we specified and append/remove those <link /> tags as needed. Note that specifying a css property on the route is completely optional, as it was omitted from the '/some/route/2' example. If the route doesn't have a css property, the <head> directive will simply do nothing for that route. Note also that you can even have multiple page-specific stylesheets per route, as in the '/some/route/3' example above, where the css property is an array of relative paths to the stylesheets needed for that route.
3. You're done
Those two things setup everything that was needed and it does it, in my opinion, with the cleanest code possible.
#tennisgent's solution is great. However, I think is a little limited.
Modularity and Encapsulation in Angular goes beyond routes. Based on the way the web is moving towards component-based development, it is important to apply this in directives as well.
As you already know, in Angular we can include templates (structure) and controllers (behavior) in pages and components. AngularCSS enables the last missing piece: attaching stylesheets (presentation).
For a full solution I suggest using AngularCSS.
Supports Angular's ngRoute, UI Router, directives, controllers and services.
Doesn't required to have ng-app in the <html> tag. This is important when you have multiple apps running on the same page
You can customize where the stylesheets are injected: head, body, custom selector, etc...
Supports preloading, persisting and cache busting
Supports media queries and optimizes page load via matchMedia API
https://github.com/door3/angular-css
Here are some examples:
Routes
$routeProvider
.when('/page1', {
templateUrl: 'page1/page1.html',
controller: 'page1Ctrl',
/* Now you can bind css to routes */
css: 'page1/page1.css'
})
.when('/page2', {
templateUrl: 'page2/page2.html',
controller: 'page2Ctrl',
/* You can also enable features like bust cache, persist and preload */
css: {
href: 'page2/page2.css',
bustCache: true
}
})
.when('/page3', {
templateUrl: 'page3/page3.html',
controller: 'page3Ctrl',
/* This is how you can include multiple stylesheets */
css: ['page3/page3.css','page3/page3-2.css']
})
.when('/page4', {
templateUrl: 'page4/page4.html',
controller: 'page4Ctrl',
css: [
{
href: 'page4/page4.css',
persist: true
}, {
href: 'page4/page4.mobile.css',
/* Media Query support via window.matchMedia API
* This will only add the stylesheet if the breakpoint matches */
media: 'screen and (max-width : 768px)'
}, {
href: 'page4/page4.print.css',
media: 'print'
}
]
});
Directives
myApp.directive('myDirective', function () {
return {
restrict: 'E',
templateUrl: 'my-directive/my-directive.html',
css: 'my-directive/my-directive.css'
}
});
Additionally, you can use the $css service for edge cases:
myApp.controller('pageCtrl', function ($scope, $css) {
// Binds stylesheet(s) to scope create/destroy events (recommended over add/remove)
$css.bind({
href: 'my-page/my-page.css'
}, $scope);
// Simply add stylesheet(s)
$css.add('my-page/my-page.css');
// Simply remove stylesheet(s)
$css.remove(['my-page/my-page.css','my-page/my-page2.css']);
// Remove all stylesheets
$css.removeAll();
});
You can read more about AngularCSS here:
http://door3.com/insights/introducing-angularcss-css-demand-angularjs
Could append a new stylesheet to head within $routeProvider. For simplicity am using a string but could create new link element also, or create a service for stylesheets
/* check if already exists first - note ID used on link element*/
/* could also track within scope object*/
if( !angular.element('link#myViewName').length){
angular.element('head').append('<link id="myViewName" href="myViewName.css" rel="stylesheet">');
}
Biggest benefit of prelaoding in page is any background images will already exist, and less lieklyhood of FOUC
#sz3, funny enough today I had to do exactly what you were trying to achieve: 'load a specific CSS file only when a user access' a specific page. So I used the solution above.
But I am here to answer your last question: 'where exactly should I put the code. Any ideas?'
You were right including the code into the resolve, but you need to change a bit the format.
Take a look at the code below:
.when('/home', {
title:'Home - ' + siteName,
bodyClass: 'home',
templateUrl: function(params) {
return 'views/home.html';
},
controler: 'homeCtrl',
resolve: {
style : function(){
/* check if already exists first - note ID used on link element*/
/* could also track within scope object*/
if( !angular.element('link#mobile').length){
angular.element('head').append('<link id="home" href="home.css" rel="stylesheet">');
}
}
}
})
I've just tested and it's working fine, it injects the html and it loads my 'home.css' only when I hit the '/home' route.
Full explanation can be found here, but basically resolve: should get an object in the format
{
'key' : string or function()
}
You can name the 'key' anything you like - in my case I called 'style'.
Then for the value you have two options:
If it's a string, then it is an alias for a service.
If it's function, then it is injected and the return value is treated
as the dependency.
The main point here is that the code inside the function is going to be executed before before the controller is instantiated and the $routeChangeSuccess event is fired.
Hope that helps.
Awesome, thank you!! Just had to make a few adjustments to get it working with ui-router:
var app = app || angular.module('app', []);
app.directive('head', ['$rootScope', '$compile', '$state', function ($rootScope, $compile, $state) {
return {
restrict: 'E',
link: function ($scope, elem, attrs, ctrls) {
var html = '<link rel="stylesheet" ng-repeat="(routeCtrl, cssUrl) in routeStyles" ng-href="{{cssUrl}}" />';
var el = $compile(html)($scope)
elem.append(el);
$scope.routeStyles = {};
function applyStyles(state, action) {
var sheets = state ? state.css : null;
if (state.parent) {
var parentState = $state.get(state.parent)
applyStyles(parentState, action);
}
if (sheets) {
if (!Array.isArray(sheets)) {
sheets = [sheets];
}
angular.forEach(sheets, function (sheet) {
action(sheet);
});
}
}
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
applyStyles(fromState, function(sheet) {
delete $scope.routeStyles[sheet];
console.log('>> remove >> ', sheet);
});
applyStyles(toState, function(sheet) {
$scope.routeStyles[sheet] = sheet;
console.log('>> add >> ', sheet);
});
});
}
}
}]);
If you only need your CSS to be applied to one specific view, I'm using this handy snippet inside my controller:
$("body").addClass("mystate");
$scope.$on("$destroy", function() {
$("body").removeClass("mystate");
});
This will add a class to my body tag when the state loads, and remove it when the state is destroyed (i.e. someone changes pages). This solves my related problem of only needing CSS to be applied to one state in my application.
'use strict';
angular.module('app')
.run(
[
'$rootScope', '$state', '$stateParams',
function($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}
]
)
.config(
[
'$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider
.otherwise('/app/dashboard');
$stateProvider
.state('app', {
abstract: true,
url: '/app',
templateUrl: 'views/layout.html'
})
.state('app.dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard.html',
ncyBreadcrumb: {
label: 'Dashboard',
description: ''
},
resolve: {
deps: [
'$ocLazyLoad',
function($ocLazyLoad) {
return $ocLazyLoad.load({
serie: true,
files: [
'lib/jquery/charts/sparkline/jquery.sparkline.js',
'lib/jquery/charts/easypiechart/jquery.easypiechart.js',
'lib/jquery/charts/flot/jquery.flot.js',
'lib/jquery/charts/flot/jquery.flot.resize.js',
'lib/jquery/charts/flot/jquery.flot.pie.js',
'lib/jquery/charts/flot/jquery.flot.tooltip.js',
'lib/jquery/charts/flot/jquery.flot.orderBars.js',
'app/controllers/dashboard.js',
'app/directives/realtimechart.js'
]
});
}
]
}
})
.state('ram', {
abstract: true,
url: '/ram',
templateUrl: 'views/layout-ram.html'
})
.state('ram.dashboard', {
url: '/dashboard',
templateUrl: 'views/dashboard-ram.html',
ncyBreadcrumb: {
label: 'test'
},
resolve: {
deps: [
'$ocLazyLoad',
function($ocLazyLoad) {
return $ocLazyLoad.load({
serie: true,
files: [
'lib/jquery/charts/sparkline/jquery.sparkline.js',
'lib/jquery/charts/easypiechart/jquery.easypiechart.js',
'lib/jquery/charts/flot/jquery.flot.js',
'lib/jquery/charts/flot/jquery.flot.resize.js',
'lib/jquery/charts/flot/jquery.flot.pie.js',
'lib/jquery/charts/flot/jquery.flot.tooltip.js',
'lib/jquery/charts/flot/jquery.flot.orderBars.js',
'app/controllers/dashboard.js',
'app/directives/realtimechart.js'
]
});
}
]
}
})
);

Resources