Angular service overrides itself when called more than once - javascript

I have a chat component that have a hash id of that chat. All my api call's (done by the service) have a hash value. When I call my chat component twice, the service hash value of the first chat gets overwritten by the seconds chat.
angular.module('testModule', [])
.controller('testController', function(testService, $scope) {
var vm = this;
vm.init = function() {
vm.hash = vm.hash();
testService.setHash(vm.hash);
}
vm.getServiceHash = function() {
vm.serviceHash = testService.hash;
}
vm.init();
})
.service('testService', function() {
var testService = {
hash: null
};
testService.setHash = function(hash) {
testService.hash = hash;
}
return testService;
})
.directive('test', function() {
return {
restrict: 'E',
template: $("#test\\.html").html(),
controller: 'testController',
controllerAs: 'test',
bindToController: {
hash: '&',
},
scope: {}
}
});
var app = angular.module('myApp', ['testModule']);
app.controller('myController', function($scope) {})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="myController">
<test hash="'123'"></test>
<test hash="'321'"></test>
</div>
<script type="text/ng-template" id="test.html">
<p>
<h2> Controller hash: {{test.hash}} </h2>
<button type="button" ng-click="test.getServiceHash()">Get service hash</button>
<h2> Service hash: {{test.serviceHash }} </h2>
</p>
</script>
</body>

As #jjmontes noted in a comment, services are singletons in Angular. As such, they should not maintain any state unless that state applies to all consumers. For example, a common set of data that applies to all consumers can be retrieved once and then used by all rather than making a potentially expensive call again. However, any controller-specific data should be maintained in the controller and passed to the service on demand.
Specifically in your case, instead of setting the hash value on the service, you should pass it as a parameter to the service method that the controller instances call.
.service('testService', function() {
var testService = {
};
testService.callService = function(hash) {
// call an API or whatever, passing the hash
}
return testService;
})

Related

Two way binding angular component with parent controller

I have an angular component (vendor-filters) that I would like to pass data to and from a parent controller. The purpose is to create a filter off of mainInfo, and pass that data back to the parent controller where it will reflect the filtering in the component. My problem is that this mainInfo variable is returning undefined in the component controller. Here's my code :
html
<div ng-controller="kanban as ctrl">
<vendor-filters mainInfo="ctrl.mainInfo"></vendor-filters>
<div class="qt-kb">
<kanban-list label="Incoming" type="incoming" cards="ctrl.mainInfo.incoming"></kanban-list>
<kanban-list label="In Progress" type="progress" cards="ctrl.mainInfo.inProgress"></kanban-list>
<kanban-list label="Waiting For Documentation" type="docs" cards="ctrl.mainInfo.documentation"></kanban-list>
<kanban-list label="Pending Approval" type="closed" cards="ctrl.mainInfo.closed"></kanban-list>
</div>
Parent Controller :
app.controller("kanban", ["$scope", "assignmentDataService", "globalSpinner", function ($scope, assignmentDataService, globalSpinner) {
const vm = this;
vm.mainInfo = [];
activate();
function activate() {
getData();
}
function getData() {
var promise = assignmentDataService.getData()
.then(function(data) {
vm.mainInfo = data;
});
globalSpinner.register(promise);
};
}]);
Component controller:
class VendorFilterCtrl {
constructor($http, $scope, $timeout,assignmentDataService) {
this.$scope = $scope
this.$http = $http;
const vm = this;
//I could be initializing this wrong but this is where I'm trying to get
//the data.
vm.data = vm.mainInfo;
}
app.controller('kanban').component("vendorFilters", {
templateUrl: "app/components/vendorFilters.html",
bindings: {
store: "=?store",
onChange: '&',
mainInfo: '<'
},
controller: VendorFilterCtrl,
controllerAs: "ctrl",
bindToController: true
});
Basically I'm trying to get the mainInfo from the parent controller into the component and visa versa. Any idea why this isn't working?
Start by using kebab-case for the attribute:
<vendor-filters ̶m̶a̶i̶n̶I̶n̶f̶o̶ main-info="ctrl.mainInfo"></vendor-filters>
NEXT
Fix this:
app ̶.̶c̶o̶n̶t̶r̶o̶l̶l̶e̶r̶(̶'̶k̶a̶n̶b̶a̶n̶'̶)̶ .component("vendorFilters", {

Navigation bar in angular 1.5 app

Hello I'm learning Angular and I'm trying to do a global navigation bar across all angular (1.5) app.
So, to do that navigation bar I've created a directive
navigation.html
<nav class="navbar navbar-default" id="header">
...
non important tags
...
<li ng-if="!session.logged">Enter</li>
<li ng-if="session.logged"><a ng-click="logout()">Logout</a></li>
</nav>
navigation.js
export default function navigation(Auth, Session) {
return {
restrict: "E",
replace: true,
scope: {},
templateUrl: directivesPath + "navigation.html",
link: function(scope, element, attrs) {
scope.session = Session.sessionData();
scope.logout = function() {
Auth.logout();
scope.session = Session.sessionData();
window.location = "#/";
}
}
}
}
I've put than directive in an index.html like this
<!DOCTYPE html>
<html lang="es" ng-app="movieApp">
<head>
...
</head>
<body>
<navigation></navigation>
<div ui-view></div>
</body>
</html>
And I have controllers, for example these two
UsersCtrl.$inject = ["$scope", "$http", "Session"];
export function UsersCtrl($scope, $http, Session) {
$http.get('http://localhost:3000/users').success(function (data) {
console.log(data);
$scope.users = data.data;
$scope.session= Session.sessionData();
});
}
export default angular.module('movieControllers').controller("LoginCtrl", ["$scope", "$rootScope", "Auth", "Session",
function($scope, $rootScope, Auth, Session) {
console.log("User is logged? " + Auth.loggedIn());
if (Auth.loggedIn() === true) {
window.location = "#/users/" + Auth.currentUser.username;
}
const button = document.getElementById('login');
button.addEventListener("click", function() {
const username = document.login.username.value;
const password = document.login.password.value;
Auth.login({username: username, password: password}, function() {
$scope.session= Session.sessionData();
window.location = "#/users/" + username;
// $scope.$digest();
});
});
}]);
The Auth and Session services just make a call to the backend, keep the
user data and retrieve that user data.
The problem is that when I login, the app redirect to the show of the user, but
the nav still showing <li>Enter</li>, but if I refresh the browser the nav show
correctly the <li>Logout</li> ant the <li>Enter</li> is hidding.
If I put the navigation directive inside the template of a controller it
behaves correctly.
What I'm doing incorrectly?
(Sorry for my English, I'm still learning the language too)
Your directive does not watch for session changes. When you assign your session object in your link function you may wrap it in $apply call to inform about the changes, like that:
function navigation(Auth, Session) {
return {
restrict: "E",
replace: true,
scope: {},
templateUrl: directivesPath + "navigation.html",
link: function(scope, element, attrs) {
scope.$apply(function() {
scope.session = Session.sessionData();
});
scope.logout = function() {
Auth.logout();
scope.$apply(function() {
scope.session = Session.sessionData();
});
window.location = "#/";
}
}
}
}
Better yet, look how isolate scope in directives work (https://docs.angularjs.org/guide/directive) and check $watch function as well.

Angular multiple directives with shared data model

The main point of this post is that I want to avoid using $scope.$watch as it is taught to cause a performance decrease.
So imagine having one shared view partial/template, call it "mypage" with two different directives, call them "directive1" and "directive2", in it that share a data model, lets call it "awesomeData"
Maybe it looks something like this:
<div class="mypage-root">
<directive1 shared-data="awesomeData"></directive1>
<directive2 shared-data="awesomeData"></directive2>
</div>
Now obviously when "awesomeData" changes in either directive or the root view, the data changes in the other parts too (assuming it is two-way bound).
But what if i want something else to happen in directive2 when directive1 has updated the data model, like calling a function in directive2?
I could use a watcher but as mentioned, that is a performance decrease.
What other approaches are there and what is the "true" angular way to do this?
I know you're asking about how to do this without a watcher, but I think in this instance a watcher is the best answer as it gives separation of concerns. directive1 shouldn't have to know about directive2, but only about MySharedService and anything it might broadcast:
$rootScope.$broadcast('myVar.updated');
...
$scope.$on('myVar.updated', function (event, args) {
});
Edit:
Also, depending on the complexity of the model the directives will be using, you could pass this model from their parent controller (The down side to this is that you have to instantiate a model from a parent):
HTML:
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angularjs#1.4.9" data-semver="1.4.9" src="https://code.angularjs.org/1.4.9/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body ng-controller="MyCtrl">
<directive1 my-model="model"></directive1>
<directive2 my-model="model"></directive2>
</body>
</html>
JS:
var app = angular.module('app', []);
app.controller('MyCtrl', function($scope) {
$scope.model = { info: 'Starting...' };
});
app.directive('directive1', function() {
var controllerFn = function($scope) {
$scope.changeInfo = function() {
$scope.vm.myModel.info = 'Directive 1';
};
};
return {
restrict: 'E',
controller: controllerFn,
template: '<div><span>hello</span> <span ng-bind="vm.myModel.info"></span> <button ng-click="changeInfo()">Click</button></div>',
controllerAs: 'vm',
bindToController: true,
scope: {
myModel: '='
}
}
});
app.directive('directive2', function() {
var controllerFn = function($scope) {
$scope.changeInfo = function() {
$scope.vm.myModel.info = 'Directive 2';
};
};
return {
restrict: 'E',
controller: controllerFn,
template: '<div<span>bye</span> <span ng-bind="vm.myModel.info"></span> <button ng-click="changeInfo()">Click</button></div>',
controllerAs: 'vm',
bindToController: true,
scope: {
myModel: '='
}
}
});
plunker: https://plnkr.co/edit/EwdHFl7eZl2rd43UVG4J?p=preview
Edit 2:
As with method 2, you could have a shared service, that provides this model:
plunker: https://plnkr.co/edit/EwdHFl7eZl2rd43UVG4J?p=preview

Using AngularJS with a factory and a Controller

I'm trying to do the following:
When a user accesses "localhost/people/:id", the information about the respective person is taken from a MongoDB and displayed via Angular.
I have my api, which works perfectly fine, I've double-checked.
I'm using the latest AngularJS (1.4.9) and the new Router (angular-new-router or ngNewRouter).
I have an Angular module:
var personModule = angular.module('app.personDetailed', []);
a factory:
personModule.factory('personService', ['$http', function($http) {
return {
get : function(id) {
return $http.get('/api/people/' + id);
}
}
}]);
and a controller:
personModule.controller('PersonDetailedController', ['$routeParams', '$scope', 'personService', PersonDetailedController]);
function PersonDetailedController($routeParams, $scope, personService) {
var id = $routeParams.id;
personService.get(id).then(function(res) {
$scope.item = res.data;
});
}
This all should be displayed in this view:
<div data-ng-controller="PersonDetailedController">
<h2>{{ item }}</h2>
</div>
(yes, I'm not bothering trying to parse json yet).
The problem is, I am unable to use both $scope and $routeParams at the same time. I can only have one or the other. If I use both, the $scope works fine, but the $routeParams is empty.
Here's the main controller, just in case:
var appModule = angular.module('app', ['app.main', 'app.personDetailed', 'ngNewRouter', 'ngResource']);
appModule.controller('AppController', ['$router', AppController]);
function AppController($router) {
$router.config([
{ path: '/', component: 'main'}
{ path: '/people/:id', component: 'personDetailed'}
]);
}
Seems the new router does away with $scope and binds to the controller instance in the template instead.
Looks like you should use this instead
personModule.controller('PersonDetailedController', ['$routeParams', 'personService', PersonDetailedController]);
function PersonDetailedController($routeParams, personService) {
var personDetailed = this,
id = $routeParams.id;
personService.get(id).then(function(res) {
personDetailed.item = res.data;
});
}
and your view (do not use ng-controller)
<h2>{{ personDetailed.item }}</h2>

Angular App: How to sync call of multiple views

I am building an angular single page app, which have a structure like this.
"app.parent - parent state"
"app.parent.childState - child state"
"app.parent.childSatate" has 4 multiple named view inside it.
I have to show something on parent once all 4 views fetched their respective data.
Any suggestions how to do it?
Note: If Solution is pure independent it helps me alot, if I delete/add any controller then i need not to make changes to parent all the time.
Suppose you have 4 services that you know will be the data source for 4 different child views. You can setup your services so that they return references, but still provide access to the underlying promises. The idea is that you want your views to use the reference when rendering the view; the parent will use $q.all to wait until the individual promises are resolved before it shows something.
Factories
app.factory('service1', function($http) {
var data1 = [];
return {
promise: $http({...}).success(function(result) {
angular.copy(result, data1);
}),
getData: function() {
return data1;
}
}
});
app.factory('service2', function($http) {
var data2 = [];
return {
promise: $http({...}).success(function(result) {
angular.copy(result, data2);
}),
getData: function() {
return data2;
}
}
});
app.factory('service3', function($http) {
var data3 = [];
return {
promise: $http({...}).success(function(result) {
angular.copy(result, data3);
}),
getData: function() {
return data3;
}
}
});
app.factory('service4', function($http) {
var data4 = [];
return {
promise: $http({...}).success(function(result) {
angular.copy(result, data1);
}),
getData: function() {
return data4;
}
}
});
Child Controllers
app.controller('ctrl1', function($scope, service1) {
$scope.data1 = service1.getData();
});
app.controller('ctrl2', function($scope, service2) {
$scope.data2 = service2.getData();
});
app.controller('ctrl3', function($scope, service3) {
$scope.data3 = service3.getData();
});
app.controller('ctrl4', function($scope, service4) {
$scope.data4 = service4.getData();
});
Parent Controller
app.controller('parent', function($scope, $q, service1, service2, service3, service4) {
$q.all(
[service1.promise,
service2.promise,
service3.promise,
service4.promise])
.then(function() {
$scope.done = true;
});
});
Parent View
<div ng-show="done"> All child data loaded </div>
A More Modular Approach
As mentioned in the original post - it would be nice if the parent controller did not have to depend on the injection of the individual data sources. That way, when the child changes data sources (adds sources, or removes sources), the parent controller is not impacted.
This can be done by relying on directive-to-directive communication. The idea is that the child directives can register their data sources with the parent directive. Once all the data sources have been registered, the parent directive can use $q.all as in the first example.
Parent Directive
app.directive('parent', function($q) {
return {
restrict: 'A',
require: 'parent',
controller: function($scope) {
$scope.done = false;
var promises = [];
this.register = function(promise) {
promises.push(promise);
}
this.getPromises = function() {
return promises;
}
},
link: function(scope, element, attr, parent) {
$q.all(parent.getPromises())
.then(function() {
scope.done = true;
});
}
}
});
Child Directive
app.directive('child', function(service1) {
return {
restrict: 'A',
require: '^parent',
controller: function($scope) {
$scope.data1 = service1.getData();
},
link: function(scope, element, attr, parent) {
parent.register(service1.promise);
}
}
});
HTML
<div parent>
<div child>
<div> {{data1}} </div>
</div>
</div>
Yet Another Approach
You may have noticed that although the second approach does not impact the parent controller, the child controller has a dependency on the parent directive. How can we eliminate this view dependency from the child controller?
Use services. Create a parent service that allows child controllers to register their promises; and also expose a method from the parent service that returns a promise which is resolved when all the child promises have been resolved.
This may be the preferred approach when you have a single point of loading for the entire page.
Parent Service
app.factory('parentService', function($q) {
var promises = [];
return {
promise: function() {
return $q.all(promises);
},
register: function(promise) {
promises.push(promise);
}
}
});
Child Controller
app.controller('childCtrl1', function(parentService, service1) {
parentService.register(service1.promise);
});
app.controller('childCtrl2', function(parentService, service2) {
parentService.register(service2.promise);
});
Parent Directive
app.directive('parent', function(parentService) {
return {
restrict: 'A',
controller: function($scope) {
$scope.done = false;
},
link: function(scope) {
parentService.promise().then(function() {
scope.done = true;
});
}
}
});
HTML
<div parent>
<div ng-show="done">All child data loaded</div>
<div ng-controller="childCtrl1">
{{ data1 }}
</div>
<div ng-controller="childCtrl2">
{{ data2 }}
</div>
</div>
Use $rootScope (recommended factory/service) in all four controllers (for all 4 views), and have certain count/flag which keeps track of data fetching in all controllers and on the main page/controller keep checking for that $rootScope/factory value.
$rootScope/ facetory/services are accessible across controllers/views, so that you can modify in several controllers and access the latest value on some other controller/factory/service.
Related documentations:
https://docs.angularjs.org/api/ng/service/$rootScope
https://docs.angularjs.org/guide/services
Create a service or factory and inject it into the controllers you need it in, that way you will not have to repeat code.
You can also use this method to share data across your app by making variables avalible via dependency injection.

Categories