AngularJS Initializing a Navbar Service - json

I've written a directive and associated controller for a navbar, which shares data with several services, like user information. I want to create a service to pull down JSON that will populate the currently hardcoded links of the navbar, and which will be shared outside the component through $route resolve etc so the currently active link and its JSON is available to different views etc.
When and where is the best place to initialize a component service like a navbar using $http that will only need to make a request once, and not have the navbar missing at any point onload? Using provider and dropping it in .config seems heavy handed according to the docs, but it's the only provider available in .config, the shorthand .service and .factory are not.
Using service with .run():
(function() {
'use strict';
/* Navbar Component */
angular.module('component.navbar', [
'component.navbar.controller',
'component.navbar.directive',
'component.navbar.service',
])
.run(function( NavbarService ) {
NavbarService.getJSON(); // handle promise here and set NavbarService.navbarJSON using a setter?
});
})();
(function() {
'use strict';
/* Service */
angular.module('cfbc.component.navbar.service', [])
.factory('NavbarService', ['$http', function( $http ) {
var that = this;
this.navbarJSON = '';
this.getJSON = function() {
$http.get('app/data/cfbc-loan-types.json')
.success( function( data ) {
that.navbarJSON = data; // can't use $scope it seems throughs an error
});
}
}]);
})();

Put the initialization logic in the .run() block instead of the .config() block so that your service can make use of $http to initialize itself at application initialization, especially if this is an application-wide service that will be consumed by multiple controllers/routes.

Related

Can a service in Angularjs be used as tool for data transfer between controllers?

I have worked with this example to generate a data transfer between two different controllers in AngularJs, but it doesnt seem to work. Specifically I can change a variable of the service from the second controller and show it in its view, but the change will not show on the first controller. There, the variable has the original value which it has in the service.
I think it has something to do with the life cycle of the controller, and maybe I need to call the sencond controller first from the frist controller so the changes can be made? Also the code I am working with is different from the exmaple meaning I have a view for every controller and I am not using ng-controller in the html as in the exmaple.
Service This is my service. I want to overwrite itemId with value from a "foreign" controller, and than use it in my controller dashctrl which is connected to my dashboard.html.
var itemId = 10;
this.getData = function()
{
//console.log('in the Get Function');
return itemId;
};
this.setData = function(Id)
{
//console.log('in the Set Function');
itemId = Id;
};
Controller 1 This is the dashCtrl Controler where I read the variable itemId.
"Foreign" controller This is my foreign controller. I am trying to overwrite the variable from here so then I can use it in the dashboard.html which is only connected to dashCtrl, but its not working. This only shows the initial value of 10 of the service and nothing is overwritten. Now if I do the same with dashctrl the overwrite will work.
The view in html Screenshot from dashboard.html, calling the variable from dashCtrl.
Yes, it's possible and it was one the ways to handle or sync data between controllers in angularjs, we used services and factories.
I have a plnkr with an online demo here: https://plnkr.co/edit/ckV5P6nezNLpqvKK0vHm?preview
Basically, you need the service:
var app = angular.module('myApp', []);
app.service("myService", function() {
var c = 10;
this.theC = function () {
return c;
};
this.updateC = function () {
c++;
}
In the app Controller you will have:
app.controller('myCtrl1', [
'$scope',
'$rootScope',
'myService',
function($scope, $rootScope, myService) {
$scope.name = "Control 1";
$scope.service = myService;
}
]);
Finally in the template:
<div class="col-xs-6">
<h3>The Service</h3>
Shared Parameter: {{service.theC()}}
<br/>
<span class="btn btn-primary" ng-click="service.updateC()">Add to Shared</span>
</div>

How controllers and functions communicate between two separate files using .emit and how to tell controller hierarchy in AngularJS?

This is my first post on stack overflow so I am really green and really new with AngularJS and ASP.Net and having a lot of problems with $rootscope.$emit. From what I have read online $rootscopes are parent scopes so all values exposed there are visible to all controllers and templates and scopes are functions inside of controllers. It seems like you can "emit" up through the controller hierarchy a call to another controller by using $rootscope.$emit("Name of $rootscope.$on function name") the $rootscope.$on listens for that call and then does whatever is in its function. The thing I am having trouble with is when I do my
$rootscope.$emit("LoadPrintDetailsModal", {});
it never seems to reach
$rootscope.$on("LoadPrintDetailsModal", function(event,args) {}.
So the question is am I misunderstanding how $emit or how controller hierarchy works or is there a problem in my code?
I have already tried using emit and I hit the debugger in indexController.js file after a call from a button in my Index.cshtml file but then when I make the
$rootScope.$emit("LoadPrintDetailsModal", {});
it does not get picked up by my printableController.js file where
$rootScope.$emit("LoadPrintDetailsModal", function (event, args) {});
// (Index.cshtml) Button in Index.cshtml file that calls "LoadPrintModal" //function in indexController
<button type="button" data-toggle="modal" data-target="#dvPrintModal"
ng-click="LoadPrintModal()">
Print
</button>
// (indexController.js)scope.LoadPrintModal in indexController.js that tries
// to emit "LoadPrintDetails" to $rootscope.%on("LoadPrintDetailsModal",
// function (event, args) in printableçontroller.js
$scope.LoadPrintModal = function () {
debugger;
$rootScope.$emit("LoadPrintDetailsModal", {});
};
// (printableController.js) file where rootScope.on is located and is supposed to pick up the emit
app.controller('PrintableController', function ($scope, $rootScope) {
$rootScope.$on("LoadPrintDetailsModal", function (event, args) {
debugger;
$scope.printModal();
});
$scope.printModal = function () {
console.log("Hello World");
};
)};
The expected result should be a console log of hello world and we should hit the debugger in printableController.js file
Use $rootScope.$broadcast:
$rootScope.$broadcast("LoadPrintDetailsModal", {});
The $broadcast method dispatches events down the scope heirarchy.
The $emit method dispatches events up the heirarchy.
For more information, see
AngularJS Developer Guide - Scope Event Propagation
To capture broadcast events, use $scope.$on:
app.controller('PrintableController', function ($scope, $rootScope) {
̶$̶r̶o̶o̶t̶S̶c̶o̶p̶e̶.̶$̶o̶n̶(̶"̶L̶o̶a̶d̶P̶r̶i̶n̶t̶D̶e̶t̶a̶i̶l̶s̶M̶o̶d̶a̶l̶"̶,̶ ̶f̶u̶n̶c̶t̶i̶o̶n̶ ̶(̶e̶v̶e̶n̶t̶,̶ ̶a̶r̶g̶s̶)̶ ̶{̶
$scope.$on("LoadPrintDetailsModal", function (event, args) {
debugger;
$scope.printModal();
});
$scope.printModal = function () {
console.log("Hello World");
};
)};
From the Docs:
Only use .$broadcast(), .$emit() and .$on() for atomic events
Events that are relevant globally across the entire app (such as a user authenticating or the app closing). If you want events specific to modules, services or widgets you should consider Services, Directive Controllers, or 3rd Party Libs
Injecting services and calling methods directly is also useful for direct communication
Directives are able to directly communicate
For more information, seed
AngularJS Wiki - Best Practices
It's hard to know what is happening without:
seeing the hierarchy of the components. Is the emitting component is down in the hierarchy from the catching component. $emit is sending messages up. $browdcast is sending messages down.
Seeing how you inject $rootscope.
Regarding 2. $rootscope injection gives you the main $scope of the application. Doing $emit from it won't gives us anything as there are no $scopes that are above the $rootScope.
My guess you want to inject $scope which represent the specific scope in the hierarchy for that controller/component. Then $emiting will propagate upward to the catching controller/component properly.

Prefetch resources for async routes

Is there a way to prefetch or preload async routes? I'm exploring how to do this right now with RR2/3. The basic idea is I code split on every route but I'd like to be able to cache bundles for connected pages in a service worker before visiting that page. So what I'd like to do is have a custom <Link> that every time it's rendered, it tries to cache the resources of the page it's linked to. This would make page transitions considerably faster. What I don't know is if there's a way to emulate navigating to a route so that the resources will be fetched. Is there an API for this or some sort of tricky way to do this someone can think of?
This is what I came up. It's a component that wraps the React Router Link component and in componentDidMount (so only runs on the client not the server) check if in production (no need to run this during development) and if this is a browser that doesn't support Service Workers (this check is specific to my use case). Then manually match against the location and call any async getComponent functions.
import React from 'react'
import Link from 'react-router/lib/Link'
class GatsbyLink extends React.Component {
componentDidMount () {
// Only enable prefetching of Link resources in production and for browsers that
// don't support service workers *cough* Safari/IE *cough*.
if (process.env.NODE_ENV === 'production' && !('serviceWorker' in navigator)) {
const routes = require('my-routes')
const { createMemoryHistory } = require('history')
const matchRoutes = require('react-router/lib/matchRoutes')
const getComponents = require('react-router/lib/getComponents')
const createLocation = createMemoryHistory().createLocation
if (typeof routes !== 'undefined') {
matchRoutes([routes], createLocation(this.props.to), (error, nextState) => {
getComponents(nextState, () => console.log('loaded bundle(s) for ' + this.props.to))
})
}
}
}
render () {
return <Link {...this.props} />
}
}
module.exports = GatsbyLink
You could just do a require.ensure... section in a timeout when the Link is mounted. That should require the code split and load it up async. The timeout will ensure it get's loaded in a separate file.
I would recommend using RR4 for code splitting as I found in RR3 the async required routes get re-included and re-rendered if a child route changes. In my case, I had the componentWillMount of my routes being fired for any child route changes. e.g. Navigating from /agent/step-1 to /agent/step-2 will cause the Component for /agent to be unmounted and re-mounted.

AngularJs Dynamic/Multiple HTML Templates

I'm working on an AngularJs/MVC app with Web API etc. which is using a CDN. I have managed to whitelist two URLs for Angular to use, a local CDN and a live CDN (web app hosted in Azure).
I can successfully ng-include a template from my local CDN domain, but the problem arises when I push the site to a UAT / Live environment, I cant be using a template on Localhost.
I need a way to be able to dynamically get the base url for the templates. The location on the server will always be the same, eg: rooturl/html/templates. I just need to be able to change the rooturl depending on the environment.
I was thinking if there was some way to store a global variable, possibly on the $rootScope somewhere that I can get to when using the templates and then set that to the url via Web API which will get return a config setting.
For example on my dev machine the var could be http://Localhost:52920/ but on my uat server it could be https://uat-cdn.com/
Any help would be greatly appreciated as I don't want to store Js, css, fonts etc on the CDN but not the HTML as it feels nasty.
Thanks I'm advance!
I think it's good practice to keep environment and global config stuff outside of Angular altogether, so it's not part of the normal build process and is harder to accidentally blow away during a deploy. One way is to include a script file containing just a single global variable:
var config = {
myBaseUrl: '/templates/',
otherStuff: 'whatever'
}
...and expose it to Angular via a service:
angular.module('myApp')
.factory('config', function () {
var config = window.config ? window.config : {}; // (or throw an error if it's not found)
// set defaults here if useful
config.myBaseUrl = config.myBaseUrl || 'defaultBaseUrlValue';
// etc
return config;
}
...so it's now injectable as a dependency anywhere you need it:
.controller('fooController', function (config, $scope), {
$scope.myBaseUrl = config.myBaseUrl;
}
Functionally speaking, this is not terribly different from dumping a global variable into $rootScope but I feel like it's a cleaner separation of app from environment.
If you decide to create a factory then it would look like this:
angular.module('myModule', [])
.factory('baseUrl', ['$location', function ($location) {
return {
getBaseUrl: function () {
return $location.hostname;
}
};
}]);
A provider could be handy if you want to make any type of customization during config.
Maybe you want to build the baseurl manually instead of using hostname property.
If you want to use it on the templates then you need to create a filter that reuses it:
angular.module('myModule').filter('anchorBuilder', ['baseUrl', function (baseUrl) {
return function (path) {
return baseUrl.getBaseUrl() + path;
}
}]);
And on the template:
EDIT
The above example was to create links but if you want to use it on a ng-include directive then you will have a function on your controller that uses the factory and returns the url.
// Template
<div ng-include src="urlBuilder('path')"></div>
//Controller
$scope.urlBuilder = function (path) {
return BaseUrl.getBaseUrl() + path;
};
Make sure to inject the factory in the controller

How to use HTML5 Local Storage with Ember.js?

I would like to use HTML5 Local Storage with my Ember.js.
I haven't been able to find any examples of doing this without Ember Data.
How should this be done? What do I need to consider?
So let's say we have an object called Storage that in our real-world implementation would represent an adapter-like object for the localStorage to store and retrieve data:
App.Storage = Ember.Object.extend({
init: function() {
this.clearStorage();
var items = ['foo', 'bar', 'baz'];
localStorage.items = JSON.stringify(items);
},
find: function(key) {
// pseudo implementation
if( !Ember.isNone(key) ) {
var items = [];
var storedItems = JSON.parse(localStorage[key]);
storedItems.forEach(function(item){
items.pushObject(item);
});
return items;
}
},
clearStorage: function() {
// pseudo implementation
localStorage.clear();
}
});
Beside the pseudo implementations, you can see there is a dummy array with some data stored at object initialization, we will use this later in our IndexRoute model hook to retrieve it, just to show that this works.
Now to the more nice stuff, you could do the register & inject directly after the application is ready, but what if we wanted it to be already available at application initialization? Well "there an ember-feature for that", called Application.initializer, initializer are simple classes with a 'name' property and a initialize function in where you have access to your application container and do what ever needs to be done, let me explain this in code:
To be notified when the application start loading we can listen to the onLoad event to create our initializer classes that will register and inject the before mentioned Storage object into every controller and every route:
Ember.onLoad('Ember.Application', function(Application) {
// Initializer for registering the Storage Object
Application.initializer({
name: "registerStorage",
initialize: function(container, application) {
application.register('storage:main', application.Storage, {singleton: true});
}
});
// Initializer for injecting the Storage Object
Application.initializer({
name: "injectStorage",
initialize: function(container, application) {
application.inject('controller', 'storage', 'storage:main');
application.inject('route', 'storage', 'storage:main');
}
});
});
Now, since the Storage object was injected into every route and every controller we can finally get access to it in our IndexRoute model hook and make the stores array mentioned above available trough the call self.get('storage').find('items') to our template to be rendered (just added a promise to make it actually conform with the ember-way and with some fictive delay, rather than just returning the array):
App.IndexRoute = Ember.Route.extend({
model: function(){
var self = this;
var promise = new Ember.RSVP.Promise(function(resolve) {
Ember.run.later(function() {
var data = self.get('storage').find('items');
console.log(data);
resolve(data);
}, 1000);
});
return promise;
}
});
In our index template we can now agnostically loop over the dummy array not caring where it is coming from:
<script type="text/x-handlebars" id="index">
<h2>Index</h2>
<ul>
{{#each item in model}}
<li>Item: {{item}}</li>
{{/each}}
</ul>
</script>
And lastly, you can see here all the above explained in a working example: http://jsbin.com/eqAfeP/2/edit
Hope it helps.
The accepted answer is great, but I thought I would add this alternative:
Dan Gebhardt has created a very interesting library called Orbit.js for coordinating different data sources on a client. There are three out of the box data sources: memory, local storage, and json api.
For ember integration, check out ember-orbit.
It is still under heavy development at this time, and it introduces a different paradigm than Ember Data, so proceed with caution!