In AngularJS, how to initialize a provider with a dynamic value - javascript

Very new to AngularJS and looking at the code in this tutorial, http://www.thinkster.io/angularjs/eIaGNOAlOk/angularjs-providers.
var app = angular.module("app",[]);
app.provider("game", function () {
var type;
return {
setType: function (value) {
type = value;
},
$get: function () {
return: {
title: type + "Craft"
};
}
};
});
app.config(function (gameProvider) {
gameProvider.setType("War");
});
app.controller("AppCtrl", function ($scope, game) {
$scope.title = game.title;
});
So in the app.config we use setType("war") but what if this value is dynamic? When the page loads the value will be provided by the server and set in a HTML data attribute, something like:
<div ng-app="app" ng-controller="AppCtrl" data-type="space">
{{ title }}
</div>
Is this possible with providers?

Yes it is can be done using another provider method and then calling it inside the config. To make it simple you can also go for the factory method and inject the factory in the config and set the dynamic value
Basic Factory:
app.module('getValue', [])
.factory('fetchValue', function($http) {
return {
getType: function(callback) {
return $http({
method: 'GET',
url: "whatever you have"
}).
success(function(data) {
callback(data);
});
}
};
});
And in you app.config you can do like
app.config(function (gameProvider,fetchValue) {
fetchValue.getType(function(data){
gameProvider.setType(data);
});
});

Related

Angular 2/4 - Is it possible to initialize a provider only after a promise is resolved?

In my provider's constructor I have something like:
constructor(
public http: Http
) {
this.http.get("api.php").toPromise().then(res=>this.res = res.json());
}
However, I only want this provider to be accessible only after this.http.get("api.php").toPromise() is resolved. Everything else should be the same as a normal provider, such as the way it is injected. Is it possible? How?
What I did with AngularJS :
initialize you attribute with var = $q.defer()
when you meet the requested value, use var.resolve(value)
And until you get the value, you can postpone treatments using var.promise.then(function (data) { ... })
Whole code of a service :
angular.module('myApp')
.service('myService', ['$http', '$q', function ($http, $q) {
s.myVar = $q.defer();
s.loadMyVar = function () {
$http({
method: 'GET',
url: "somewhere"
}).then(function successCallback(response) {
s.myVar.resolve(response.data);
});
};
s.getMyVar = function () {
return s.myVar.promise.then(function (data) {
return data;
});
};
return s;
}]);

color of the knob cannot be updated after server data request

After returning data from server request, the color of the knob cannot be updated as like as given image link screencast . My code is given below:
JavaScript Code
$scope.options = { /* knob option */ };
$http
.get(url)
.then(function(res) {
$scope.details = res.data.rating // need to show $scope.details in knob
}, function(err) {});
html code
<ui-knob value="details" options="options "></ui-knob>
NB: I'm using ng-knob
I solve this problem by using the following code :
angular.module('', [])
.controller('DemoCtrl', function($scope, $timeout) {
$scope.options = { /* knob option */ };
$scope.details = 0; // value initialize
$http
.get(url)
.then(function(res) {
$timeout(function() {
$scope.details = res.data.rating // load data from server
}, 1000);
}, function(err) {});
});
//1. your code is async
//2. you need use 1. promise or 2. resolve in your router or 3. $scope.apply() after get response in your controller
//Prime 1 (Promise):
//code in Controller
var vm = this;
vm.asyncGreet = function () {
var deferred = $q.defer();
$http({
method: 'post',
data: $httpParamSerializerJQLike({
//***your-send-data***
}),
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
url: ''//***your-url***
}).
success(function (data, status) {
deferred.resolve('true');
});
return deferred.promise;
};
var promise = vm.asyncGreet();
promise.then(function (greeting) {
//alert('Success: ' + greeting);
//YOUR CODE AFTER SERVER REQUEST
$scope.details = res.data.rating;
}, function (reason) {
//alert('Failed: ' + reason);
}, function (update) {
//alert('Got notification: ' + update);
});
//Prime 2 (Resolve in your Route)
(function (angular) {
'use strict';
// route-config.js
angular
.module('app')
.config(config);
config.$inject = ['$routeProvider'];
function config($routeProvider) {
console.log('routeConfig');
$routeProvider
.when('/requests', {
templateUrl: '/tpl.html',
controller: 'requestController',
controllerAs: 'vm',
//(Promise) IS REQUEST TO SERVER, AFTER START requestController
resolve: {
requestPrepService: requestPrepService
}
});
}
requestPrepService.$inject = ['requestService'];
function requestPrepService(requestService) {
//This $http response
return requestService.getRequests();
}
})(window.angular);
//in Controller
.$inject = ['requestPrepService'];
vm.request = {
data: requestPrepService.data,
}
//Addition
1. If you use promise do not forget $inject = ['$q']
2. Please, read https://docs.angularjs.org/api/ng/directive/ngCloak
(
how use:
<div ng-controller="yourController" ng-cloak></div>
)

ui-sref blocked from accessing controller data or view

I am having some trouble getting to the controller for my state param. I am using the correct state to link to the next view.
<td><a ui-sref="orders({customerId: cust.id})">View Orders</a></td>
In my config file I am referencing the state that name and the route params. I commented out the resolve object for now. My goal is to get into the controller then pass the correct data. Notice that I am using controllerAs
My initial thought was ({customerId: ctrl.cust.id }) However that did not change the url route.
The url is changing to match the url name but is not connecting to the controller and is not giving me the view.
(function() {
'use strict';
angular
.module('app.orders')
.config(config);
function config($stateProvider) {
$stateProvider
.state('orders',{
// params: {customerid: null},
url:'/customers:customerId',
templateUrl: './components/orders/orders.html',
controller: 'OrdersController',
controllerAs: 'ctrl',
resolve: {
customerFactory: 'customerFactory',
customerInfo: function( customerFactory, $stateParams) {
return customerFactory.getCustomers($stateParams.id);
}
}
************** my main problem is the resolve. This is blocking me from getting into the next controller. *****************
resolve: {
customerId:[ '$stateParams','customerFactory', function( $stateParams, customerFactory) {
return customerFactory.getCustomers($stateParams.id);
}]
}
})
};
})();
For now my controller is very small. I just want to connect to it. I have checked my networks tab and see GET for the files.
(function() {
// 'use strict';
angular
.module('app.orders')
.controller('OrdersController', OrdersController);
function OrdersController($stateParams) {
console.log('in');
var vm = this;
vm.title = "Customer Orders";
vm.customer = null;
}
}());
I have referenced my module in the main javascript file.
(function () {
'use strict';
angular.module('app', ['app.services',
'app.customers',
'app.orders','ui.router']);
})();
When I comment out the resolve I am able to access the controller. So I know the problem is in the resolve. Here is my service. I am making a request to a Json file with $http request and using .then
Updates Here is my refactored service call I am getting back the correct customer in the console each time.
(function() {
angular
.module('app.services',[])
.constant('_', window._)
.factory('customersFactory', customersFactory);
function customersFactory($http, $log) {
return {
getCustomers: getCustomers,
getCustomer: getCustomer
};
function getCustomers(){
return $http.get('./Services/customers.json',{catch: true})
.then(getCustomerListComplete)
.catch(getCustomerListFailed);
function getCustomerListComplete(response) {
console.log('response.data',response.data);
return response.data;
}
function getCustomerListFailed(error) {
console.log('error', error);
}
}
function getCustomer(id) {
var url = './Services/customers.json';
return $http.get(url, {
catch: true
})
.then(function(response) {
console.log('promise id',id);
var data = response.data;
for(var i =0, len=data.length;i<len;i++) {
console.log('data[i].id',data[i].id);
if(data[i].id === parseInt(id)) {
console.log('data[i]', data[i]);
return data[i];
}
}
})
}
}
}());
There is a working example with your code
It is very hard to guess what is wrong. Based on suggestion I gave you here Have a expression error in ui-sref ... your code seems to be completely valid.
I placed your stuff into this app.orders.js file (the ONLY change is templateUrl path, just for plunker purposes):
angular
.module('app.orders', ['ui.router'])
'use strict';
angular
.module('app.orders')
.config(['$stateProvider', config]);
//config.$inject = ['$stateProvider'];
function config($stateProvider) {
$stateProvider
.state('orders',{
// params: {customerid: null},
url:'/customers:customerId',
//templateUrl: './components/orders/orders.html',
templateUrl: 'components/orders/orders.html',
controller: 'OrdersController',
controllerAs: 'ctrl'
// resolve: {
// customerId:[ '$stateParams','customerFactory', function( $stateParams, customerFactory) {
// return customerFactory.getCustomers($stateParams.id);
// }]
// }
})
};
// 'use strict';
angular
.module('app.orders')
.controller('OrdersController', OrdersController);
OrdersController.$inject = ['$stateParams'];
function OrdersController($stateParams) {
console.log('in');
var vm = this;
vm.title = "Customer Orders " + $stateParams.customerId;
vm.customer = null;
}
And this is the working template components/orders/orders.html:
<div >
<h3>current state name: <var>{{$state.current.name}}</var></h3>
<h5>title</h5>
<pre>{{ctrl.title}}</pre>
...
When I call it like this:
<li ng-repeat="cust in [{id:1}, {id:2}]"
><a ui-sref="orders({customerId: cust.id})">View Orders - cust ID == {{cust.id}}</a>
</li>
Check it in action here
So, whil my previous answer was about make the state working without resolve, now we will observe few adjustments (and one fix) to make even resolve working.
There is a working plunker, extending the previous one.
FIX
The only fix, the most important change come from this definition:
angular
.module('app.services',[])
.factory('customersFactory', customersFactory);
see the plural in the factory name, the 'customersFactory'. While here:
...my main problem is the resolve. This is blocking me from getting into the next controller....
resolve: {
customerId:[ '$stateParams','customerFactory', function( $stateParams, customerFactory) {
return customerFactory.getCustomers($stateParams.id);
}]
}
we ask for 'customerFactory' (singular, no s in the middle)
Few improvements:
So, this would be our adjusted state def:
$stateProvider
.state('orders',{
// INTEGER is here used to later easily use LO_DASH
url:'/customers{customerId:int}', // int is the type
templateUrl: './components/orders/orders.html',
controller: 'OrdersController',
controllerAs: 'ctrl',
resolve: {
// wrong name with 's'
//customerId:[ '$stateParams','customerFactory',
// we use customer, because we also changed the factory
// implementation - to return customer related to
// $statePrams.customerId
customer:[ '$stateParams','customersFactory',
function( $stateParams, customersFactory) {
return customersFactory
//.getCustomers($stateParams.id)
.getCustomer($stateParams.customerId)
;
}]
}
})
Now, this is our adjusted factory, and its new method getCustomer
angular
.module('app.services', [])
.factory('customersFactory', customersFactory);
customersFactory.$inject = ['$http', '$log', '$q', '$stateParams'];
function customersFactory($http, $log, $q, $stateParams) {
return {
getCustomers: getCustomers,
getCustomer: getCustomer
};
function getCustomers() {
// see plunker for this, or above in question
}
// new function
function getCustomer(id) {
var url = "customer.data.json";
return $http
.get(url, {
catch: true
})
.then(function(response){
var data = response.data;
var customer = _.find(data, {"id" : id});
return customer;
})
;
}
}
this is our data.json:
[
{
"id" : 1, "name": "Abc", "Code" : "N1"
},
{
"id" : 2, "name": "Def", "Code" : "N22"
},
{
"id" : 3, "name": "Yyz", "Code" : "N333"
}
]
And here we have controller:
OrdersController.$inject = ['$stateParams', 'customer'];
function OrdersController($stateParams, customer) {
console.log('in');
var vm = this;
vm.title = "Customer Orders " + $stateParams.customerId;
vm.customer = customer;
}
a view to show customer
<h3>customer</h3>
<pre>{{ctrl.customer | json}}</pre>
Check it here in action

Angular controller promises and testing

Im writing some unit tests for my controller which uses promises.
Basically this:
UserService.getUser($routeParams.contactId).then(function (data) {
$scope.$apply(function () {
$scope.contacts = data;
});
});
I have mocked my UserService. This is my unit test:
beforeEach(inject(function ($rootScope, $controller, $q, $routeParams) {
$routeParams.contactId = contactId;
window.localStorage.clear();
UserService = {
getUser: function () {
def = $q.defer();
return def.promise;
}
};
spyOn(UserService, 'getUser').andCallThrough();
scope = $rootScope.$new();
ctrl = $controller('ContactDetailController', {
$scope: scope,
UserService:UserService
});
}));
it('should return 1 contact', function () {
expect(scope.contacts).not.toBeDefined();
def.resolve(contact);
scope.$apply();
expect(scope.contacts.surname).toEqual('NAME');
expect(scope.contacts.email).toEqual('EMAIL');
});
This give me the following error:
Error: [$rootScope:inprog] $digest already in progress
Now removing the $scope.$apply in the controller causes the test to pass, like this:
UserService.getUser($routeParams.contactId).then(function (data) {
$scope.contacts = data;
});
However this breaks functionality of my controller... So what should I do here?
Thanks for the replies, the $apply is not happening in the UserService. It's in the controller. Like this:
EDIT:
The $apply is happening in the controller like this.
appController.controller('ContactDetailController', function ($scope, $routeParams, UserService) {
UserService.getUser($routeParams.contactId).then(function (data) {
$scope.$apply(function () {
$scope.contacts = data;
});
});
Real UserService:
function getUser(user) {
if (user === undefined) {
user = getUserId();
}
var deferred = Q.defer();
$http({
method: 'GET',
url: BASE_URL + '/users/' + user
}).success(function (user) {
deferred.resolve(user);
});
return deferred.promise;
}
There are a couple of issues in your UserService.
You're using Q, rather than $q. Hard to know exactly what effect this has, other than it's not typical when using Angular and might have affects with regards to exactly when then callbacks run.
You're actually creating a promise in getUser when you don't really need to (can be seen as an anti-pattern). The success function of the promise returned from $http promise I think is often more trouble than it's worth. In my experience, usually better to just use the standard then function, as then you can return a post-processed value for it and use standard promise chaining:
function getUser(user) {
if (user === undefined) {
user = getUserId();
}
return $http({
method: 'GET',
url: BASE_URL + '/users/' + user
}).then(function(response) {
return response.data;
});
}
Once the above is changed, the controller code can be changed to
UserService.getUser($routeParams.contactId).then(function (data) {
$scope.contacts = data;
});
Then in the test, after resolving the promise call $apply.
def.resolve(contact);
scope.$apply();

Angularjs: how to revert back to the runtime loaded DOM elements in the previous view on going back (preserve state)

I have an angular application which has two views:
1) List view
2) Detail View
when you click on the thumbnail from list view you go to detail view, here is the route:
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/list', {
templateUrl: 'partials/list.html',
controller: 'ListCtrl',
}).
when('/list/:id', {
templateUrl: 'partials/detail.html',
controller: 'DetailCtrl',
}).
otherwise({
redirectTo: '/list'
});
}]);
Now there is a function loadmore in 'listCtrl' controller which is used to load
myControllers.controller('ListCtrl', ['$scope', '$location', 'Troll', '$http',
function ($scope, $location, Troll, $http) {
$scope.Trolls = Troll.query();
$scope.orderProp = 'popular';
$scope.fromData = {};
//console.log($scope.Trolls);
//this will be used to fetch the data in json which is defined in services.js
$scope.loadmore = function () {
jQuery.ajax({
url: 'trolls/trolls.php?troll_index=' + $('#main-content #item-list .sub-item').size(),
type: 'GET',
async: false,
data: {},
dataType: 'json',
success: function (response) {
if (response != null) {
$.each(response, function (index, item) {
$scope.Trolls.push({
UID: response[index].UID,
id: response[index].id,
popular: response[index].popular,
imageUrl: response[index].imageUrl,
name: response[index].name,
tags: response[index].tags,
category: response[index].category
});
});
}
},
complete: function () {},
error: function () {
console.log('Failed!');
}
});
$scope.text = 'Hello, Angular fanatic.';
$http.get('trolls/trolls.php?troll_id=' + Troll);
}
}]);
PROBLEM:
now the problem is,
After clicking on loadmore if i go to detail view and i come back on list view my newly loaded divs are gone how do i preserve them??
When you change routes the controller that is in charge of that route is initialized when the route is loaded and destroyed when the route is changed. So the reason you lose your data is the controller is reinitialized and the previous data never exists.
There are two ways to fix this.
Higher level controller that is not destroyed - could potentially live on the body - this passes its scope to the children controllers. But this is not a true modularization of concerns. For this issue... Can be very useful for other issues - Authentication, Profile, etc.
The way I would advocate is to pull this into a service for example - listService - this will fetch and cache the data and pass it back to the listController when its reloaded, thus preventing the data from being lost.
First way to solve could be this...
So if you have a higher level controller in charge of fetching the data or move it into a service which is what I would do, then the data that is loaded from loadMore function will continue to be there, but it needs to be on a higher parent scope that is not destroyed on the route change.
HTML:
<body ng-controller="ApplicationController">
<!-- Code Here -->
</body>
Controller:
myControllers.controller('ApplicationController', function($scope) {
var data = [];
$scope.loadmore = function () {
// Use Angular here!!! $http not jQuery!
// Its possible to write a complete Angular app and not ever true jQuery
// jQuery Lite the Angular implementation will be used though
jQuery.ajax({
url: 'trolls/trolls.php?troll_index=' + $('#main-content #item-list .sub-item').size(),
type: 'GET',
async: false,
data: {},
dataType: 'json',
success: function (response) {
if (response != null) {
$.each(response, function (index, item) {
data.push({
UID: response[index].UID,
id: response[index].id,
popular: response[index].popular,
imageUrl: response[index].imageUrl,
name: response[index].name,
tags: response[index].tags,
category: response[index].category
});
});
}
return data;
}
error: function () {
console.log('Failed!');
}
});
}
});
However I dont really like this approach as its a bit hacky...and jQuery is used...
Second approach using a service to fetch/cache:
So lets pull it into a service instead.
myServices.factory('listService', function($http, $q) {
var//iable declaration
service = {},
list = []
;
/////////////////////
//Private functions//
/////////////////////
function loadMore(url) {
var deferred = $q.defer();
$http({ method: 'GET', url: url }) // Need to pass in the specific URL maybe from the DOM scoped function?
.success(function(data) {
deferred.resolve(data);
})
.error(function() {
deferred.reject();
//Do error things
});
return deferred.promise;
}
////////////////////
//Public Functions//
////////////////////
service.loadMore = function(url) {
// Used for loading more data
loadMore(url).then(function(data) {
list.push(data);
return list
});
}
service.getList = function() {
// Returns the currently loaded data
return list;
}
return service;
});
Then in your controller:
myControllers.controller('ListCtrl', ['$scope', '$location', 'Troll', listService
function ($scope, $location, Troll, listService) {
$scope.Trolls = Troll.query();
$scope.orderProp = 'popular';
$scope.fromData = {};
$scope.loadmore = function(subItemSize) { //add url specific params here
var url = 'trolls/trolls.php?troll_index=' + subItemSize;
return listService.loadMore(url);
};
}]);
use ui-router instead:
https://github.com/angular-ui/ui-router, you can have multiple ui-view and also you can use relative routes, you should define child states, this way the parent state remains unchanged when route changes to a child state, check out this video:https://egghead.io/lessons/angularjs-introduction-ui-router
i created a plunker:http://plnkr.co/edit/FA3DuHgngKD2CIPG5bHW?p=preview
You can store data in a service or cache it into the Angular cache, but your divs will still be gone when you route back to that view.
If you want to preserve the state of the DOM too, there's an extension to ui-router called ui-router-extras. It has a a nice demo with state transitions and full DOM preservation when switching among tabs.

Categories