I have an angular app which has a ng-view which (like any good MVC should) manipulates how the model is shown. The data (model) comes from a database, and I call it into the app's component. From there I want to propagate (if that's the right word) the model into ng-view, which loads a template to display the data based on the route. I also want to be able to filter the data/model that goes into the view with a "top-bar"
I.e:
INDEX.HTML:
<html ng-app="app">
<head>...</head>
<body ng-controller="appController">
<top-bar></top-bar>
<div ng-view></div>
</body>
</html>
APP.JS:
angular.module('app', ['top-bar','view-one','view-two', 'ngRoute']);
angular.module('app').controller('appController', function() {
var self = this;
this.myData = [];
$http.get('theQuery').then(res => self.myData = res.data);
});
angular.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/view-one', {template:'<view-one></view-one>'})
.when('/view-two', {template:'<view-two></view-two>'});
});
angular.module('top-bar', ['ngRoute']);
angular.module('top-bar').component('top-bar', {
templateUrl: './app/top-bar/top-bar.template.html',
controller: function(filterFilter) {
this.filters = filterFilter(...);
}
});
angular.module('view-one', ['ngRoute']);
angular.module('view-one').component('view-one', {
templateUrl: './app/view-one/view-one.template.html',
controller: function(filterFilter) {
// appController.data and topBar.filters would somehow
// need to be gotten from those respective modules.
this.data = appController.data;
this.filter = topBar.filters;
}
});
What I am trying to figure out is how to get the data from the main app's controller (appController) and the top-bar component, and send it to whatever view is currently loaded into ng-view.
I've been searching the web, I cannot find if the better way to do this would be to use binding (i.e. binding: {data:'<'})in the view-one controller/component, a system of $scopes, a custom service or something else. I also can't find out I would accomplish using either one to get the data in there. Thus any answers that also include a) code samples and b) links to further documentation I could read up on would be would be much appreciated.
The recommended way for doing this is to create a service, and let the different controllers work with the reference to the objects provided by the service.
Possible duplicate of enter link description here
Related
I am trying to run an $http function when my AngularJS application first loads.
This $http function needs to finish before any of the controllers in my application could properly function. How would I go about doing this? This sounds like a promise, but it sounds like I would be creating a promise in each controller...
I currently have the function that I want to run first like this:
app.run(function() {
$http.get('link').success(function(data) {
// success function. The data that I get from this HTTP call will be saved to a service.
}).error(function(error) {
});
});
However, sometimes the controller will load before the http call finishes.
The problem
Angular is not dynamic, you cannot add controller dynamically neither factory, etc. Also you cannot defer controller bootstrap, angular loads everything together, and it's quite disadvantage (will be fixed in Angular 2)
The cure
But javascript itself has very important feature - closure, which works anywhere, anytime.
And angular has some internal services that can be injected outside of angular ecosystem, even into browser console. Those services injected as shown below. We technically could use anything else (jQuery.ajax, window.fetch, or even with XMLHttpRequest), but let's stick with total angular solution
var $http_injected = angular.injector(["ng"]).get("$http");
The act
First of all, we defer whole angular app bootstrap, inject http service. Then you make your needed request, receive data and then closure get's to work, we pass received data into some service, or we could also assign in to some angular.constant or angular.value but let's just make demo with angular.service, so when your service has data, bootstrap whole app, so that all controllers get initialized with your needed data
Basically that kind of tasks solved like this
<body>
<div ng-controller="Controller1">
<b>Controller1</b>
{{text}}
{{setting.data.name}}
</div>
<hr>
<div ng-controller="Controller2">
<b>Controller2</b>
{{text}}
{{setting.data.name}}
</div>
<script>
//define preloader
var $http_injected = angular.injector(["ng"]).get("$http");
$http_injected.get('http://jsonplaceholder.typicode.com/users/1').then(function(successResponse) {
//define app
angular.module('app', []);
//define test controllers
//note, usually we see 'controller1 loaded' text before 'settings applied', because controller initialized with this data, but in this demo, we will not see 'controller1 loaded' text, as we use closure to assign data, so it's instantly changed
angular.module('app').controller('Controller1', function($scope, AppSetting) {
$scope.text = 'controller1 loaded';
$scope.setting = AppSetting.setting;
$scope.$watch('setting', function(e1 ,e2){
$scope.text = 'settings applied'
});
});
angular.module('app').controller('Controller2', function($scope, AppSetting) {
$scope.text = 'controller2 loaded';
$scope.setting = AppSetting.setting;
$scope.$watch('setting', function(e1 ,e2){
$scope.text = 'settings applied'
});
});
//define test services, note we assign it here, it's possible
//because of javascript awesomeness (closure)
angular.module('app').service('AppSetting', function() {
this.setting = successResponse;
});
//bootstrap app, we cannot use ng-app, as it loads app instantly
//but we bootstrap it manually when you settings come
angular.bootstrap(document.body, ['app']);
});
</script>
</body>
Plunker demo
You can do this when you configure your routes
app.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/', {
controller: 'MainCtrl',
templateUrl: 'main.html',
resolve: {
data: ['$http',
function($http)
{
return $http.get('/api/data').then(
function success(response) { return response.data.rows[0]; },
function error(reason) { return false; }
);
}
]
}
});
}]);
Similar question:
AngularJS - routeProvider resolve calling a service method
AngularJS: $routeProvider when resolve $http returns response obj instead of my obj
Heres a plunkr I found using a service, which is what I would recommend.
http://plnkr.co/edit/XKGC1h?p=info
TL;DR;
I've written a program that uses DOM-manipulation and jQuery to respond to the user inputting a comma-separated list of values in a hash-URL and wish to do it in Angular, instead.
The long version
I have been writing a program, on and off, in my spare time, that draws fractal images, such as the famous Mandelbrot fractal. Here's the URL: http://danielsadventure.info/html5fractal/docs/intro.html. I did this as an exercise to flex my HTML5 muscles with features like the canvas element and web workers. The program works great. Here is a rendered image of the "Negabrot" fractal from my program:
Recently, I've been teaching myself Angular, and I decided to rewrite the Javascript using Angular instead of jQuery. Once again, I'm doing this as an exercise.
Angular is, indeed, a very suitable tool because there are lots of forms that the user may use to describe the fractal image to be drawn. I was able to use Angular to bind a model to the forms and get rid of the DOM-manipulation code that I was previously using. Yay! Angular is awesome!
There is another feature of my program that it is not entirely clear how I should convert it to work with Angular. My program is a Single Page Application. There is only one thing it does: draw fractal images. However, I use hash-URLs to keep track of what fractal is being drawn and what configuration is used to draw it. For example, you can follow the URL below to see a zoomed-in section of the Multibrot-5 fractal:
http://danielsadventure.info/html5fractal/index.html#103,0.41000000000000014,1.0299999999999998,0.41999999999999993,1.04,2,1261,true,z%20^%205%20%2B%20c,-2,2,-2,2
As you can see, the URL consists of a list of comma-separated values that describe different aspects of the programs configuration. If you draw something beautiful with it, you can simply send someone else the URL and they can draw the same thing; easy as pie!
In order to accomplish this, I listen for an event that indicates that the hash-URL has changed and respond to it by updating the configuration on the form, once again using old-fashioned DOM-maniputation.
I previously asked on StackOverflow how to respond to hash-URLs, and I was directed to ngRoute. ngRoute looks very useful, but it looks like it is associated primarily with templates and controllers.
In my program, I need not load any additional templates. All I need is to respond to a new hash-URL by updating the configuration and drawing a new fractal. I also want to update the hash-URL with the same when the user manually updates the configuration and draws a new fractal.
In short, what I want to happen is this:
When the user enters a new hash-URL, the program should respond by updating the model that is bound to the inputs so that the form values change.
When the user manually changes the inputs and clicks a button to draw again, the hash-URL should be updated with the new configuration.
With angular ui-router you could do it like this:
angular.module('demoApp', ['ui.router'])
.config(function($stateProvider, $urlRouterProvider) {
//
// For any unmatched url, redirect to /fractal
$urlRouterProvider.otherwise("/fractal?minr=-0.29&maxr=-0.27&mini=-0.64");
//
// Now set up the states
$stateProvider
.state('fractal', {
url: '/fractal?minr&maxr&mini',
templateUrl: 'app/partials/fract.html',
controllerAs: 'fract',
controller: function($stateParams) {
console.log($stateParams);
var fract = new Fractal($stateParams);
this.getSettings = fract.getSettings;
}
});
});
In the url property you can specify your params. I've picked just some of your params.
$stateParams service will inject all the params that are passed in the url.
The following is just to show how I've implemented the Fractal class:
function Fractal(settings) {
var that = this;
this.settings = settings;
this.getSettings = function() {
return that.settings;
}
}
And the partial fract.html looks like this (it only outputs the settings):
<h1>Drawing fractal in this state</h1>
<hr/>
{{fract.getSettings()|json}}
In your app you'll probably create a directive for your fractal class because you're doing DOM stuff. I'm just adding everything in the controller of the state to keep the demo simple.
You can add the directive to the fractal.html partial.
Please have a look at the demo below or in this jsfiddle. Please notice that you're not seeing the url parameters in jsfiddle.
In your app they will be present like in the following screenshot:
angular.module('demoApp', ['ui.router'])
.config(function($stateProvider, $urlRouterProvider) {
//
// For any unmatched url, redirect to /state1
$urlRouterProvider.otherwise("/fractal?minr=-0.29&maxr=-0.27&mini=-0.64");
//
// Now set up the states
$stateProvider
.state('fractal', {
url: '/fractal?minr&maxr&mini',
templateUrl: 'app/partials/fract.html',
controllerAs: 'fract',
controller: function($stateParams) {
console.log($stateParams);
var fract = new Fractal($stateParams);
this.getSettings = fract.getSettings;
}
});
});
// here comes your fractal class
function Fractal(settings) {
var that = this;
this.settings = settings;
this.getSettings = function() {
return that.settings;
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.js"></script>
<div ng-app="demoApp">
<script type="text/ng-template" id="app/partials/fract.html">
<h1>Drawing fractal in this state</h1>
<hr/>
{{fract.getSettings()|json}}
</script>
<div ui-view></div>
<!-- We'll also add some navigation: -->
<a ui-sref="fractal({minr:-0.3, maxr:-0.2, mini: -0.64})">Draw fractal</a>
</div>
ok I am not quite sure what JS code you already have, so I am going to show some snippets you may find helpful!
This is a watch on the URL - so everytime the hash changes this function will be called:
$scope.$on('$locationChangeSuccess', function(event, newState, oldState) {
var values = $location.hash().split(','); // => [103,true,...]
var desc = ['point1', 'boolean']; // I don't know wich values you have..
$scope.values = {}; // values model
angular.forEach(values, function(value, index) {
$scope.values[desc[index]] = value; // {'point1': 103} => nested attribute
});
});
Then you can bind this to a form:
// HTML
<form ng-submit="updateHash()">
<input ng-model="values.point1" />
</form>
// JS
$scope.updateHash = function() {
var updatedValues = [];
angular.forEach($scope.values, function(value) {
updatedValues.push(value);
});
$location.hash(updatedValues); // update URL
};
I am working on an angular application that relies heavily on the client-side and renders most of the app's components through a RESTful API. One of which, is a navigation component which is stored in a JSON file, and is comprised of a list of links that use the ui-router syntax to navigate between states.
So far, I've managed to write a service that builds the nav from a json. Then, using a controller, I'm rendering it to the view using angular's sanitize service. The part I'm stuck at is, the result links appear as hard-coded strings that aren't clickable.
From different threads I read, I assume it's because they aren't compiled and just thrown to the view, but I'm unable to get them to compile / work.
I've seen similar threads such as here and here but they all relay on creating a custom directive for it. I need to render and compile dynamic html (ui-sref links in specific) that are returned from an $http service.
JSFiddle
<div ng-controller="TopNavController as TopNavCtrl">
Output Navigation :
<div ng-bind-html="topnavCtrl.navbar"></div>
</div>
<!-- json file contains something similar to this :
<ul>
<li><a ui-sref="catpage({category: 'products', subcategory: 'whale toys'})" </a>Whale Toys</li>
<li><a ui-sref="catpage({category: 'products', subcategory: 'sharks toys'})"">Shark Toys</a></li>
</ul>
-->
var myApp = angular.module('myApp',[]);
myApp.controller('TopNavController', ['NavbarFactory', '$sce', function(NavbarFactory, $sce) {
var self = this;
self.navbar ="";
NavbarFactory.getnav().then(function(data) {
self.navbar = $sce.trustAsHtml(data);
});
}])
.factory('NavbarFactory', ['$http', function($http) {
return {
getnav: function() {
return $http.get('/path/myjson').then(function(answer) {
var result = answer.data;
return result
},
function(error) {
console.log('Failed');
});
}
}
}]);
After reading both the api and the developer guide, I still don't understand the functionality provided by declaring 'controller' in a given route. Right now I just have my controllers declared as ng-controller directives in my views. Is ngRoute simply providing an alternative method?
To make my question explicit in code, see below:
--Index.html
...
<body ng-app="MyApp">
<div ng-view>
</div>
</body>
--View.html
<div id="myView" ng-controller="MyController">
...
</div>
--Route.js
var app = angular.module('MyApp', [ require('angular-route') ]);
app.controller('MyController', ['$scope', function ($scope) {
console.log('this gets executed as I would expect');
}])
.config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', { templateUrl: '/Index.html' })
.when('/view', { templateUrl: '/View.html' });
// below line makes no difference as an alternative to above
//.when('/view', { templateUrl: '/View.html', controller: 'MyController' });
}]);
There are two ways to define controller for a view.
Either in the controller declaration in the ng-route
in the ng-controller for the view.
Either one is fine.
You should pick one option over the other since using both will actually give you duplicate controllers, i.e. both will be used. If you're using Routes, then you can specify a few additional properties such as resolve which has been mentioned in the comments and this will allow you to perform an action, or supply supplementary data etc.
Take a look at this article, Using Resolve In Angular, for more information.
Also, you should look into using Controller As, which sets you up for future proofing. John Papa has a few blogs and videos where he praises the use of Controller As and using the var vm = this; style syntax, take a look here.
Also, as a side note, you should use the .otherwise in your routes as this will capture any requests that are invalid and at least serve up a valid page from your site. You can see this in the routeProvider documentation.
I´m developing an Angular-App. The user has to enter data on different pages whereas it´s possible to switch between the pages. Furthermore there is an "overview page" where the user can see the entered data.
So now im thinking about how to show the entered data on the overview page. Should i just use $rootScope to get the data or is it better to store it in JSON-objects or - but this is not suggested by Angular - store data in a service?
So where to get the entered data from the different pages?
Thanks!
If you want to persist data between actual HTML pages and not routes within a single page, you could use LocalStorage. Here is a service that will make that a little easier.
Another approach would be to use cookies. You can read more about using cookies in Angular here.
If you are wanting to persist the data across different routes, you will need to create a shared service. Here is an example of such an approach:
<div ng-app="myApp">
one | two
<div ng-view></div>
<script id="one.html" type="text/ng-template"><div><h1>Template One</h1>foo = {{shared.foo}}</div></script>
<script id="two.html" type="text/ng-template"><div><h1>Template Two</h1>foo = {{shared.foo}}</div></script>
</div>
angular.module('myApp', []).
config(function($routeProvider){
$routeProvider.
when('/one', {templateUrl: 'one.html', controller: 'OneCrtl'}).
when('/two', {templateUrl: 'two.html', controller: 'TwoCrtl'}).
otherwise({redirectTo: '/one'});
}).
service('sharedService', [function() {
this.foo = 'bar';
}]).
controller('OneCrtl', function($scope, sharedService){
$scope.shared = sharedService
}).
controller('TwoCrtl', function($scope, sharedService){
$scope.shared = sharedService
});
Here's a fiddle.