define a controller and use twice - angularjs

I want to user a controller for two separated div and when I do that , it send $http requests twice
How can i use the scope of other controllers?
here is my code :
<div data-ng-controller="productCTRL">
<span ng-model="basket | count"></span>
....Some HTML Code......
</div>
<div data-ng-controller="AuthController">
....Some HTML Code...
</div>
<div data-ng-controller="productCTRL">
<ul ng-repeat="product in products">
<li>{{product.title}}</li>
</ul>
</div>

One approach...
You could use a monolithic controller:
<div data-ng-controller="mainCTRL">
<div data-ng-controller="productCTRL">
<span ng-model="basket | count"></span>
....Some HTML Code......
</div>
<div data-ng-controller="AuthController">
....Some HTML Code...
</div>
<div data-ng-controller="productCTRL">
<ul ng-repeat="product in products">
<li>{{product.title}}</li>
</ul>
</div>
</div>
Then if you make the $http call in mainCTRL instead of productCTRL, it will run just once when the view is rendered. You could then access the data held in mainCTRL from productCTRL via scope inheritance.
Another approach...
You could move the $http call to an angular service or factory. Since these are singletons, only one instance will ever exist and that means that only one $http call would be made when the app is first loaded. You would then store the returned data locally in the service and expose it publicly.
Here is a simple implementation as a factory:
app.factory("productService", function($http){
var products = [];
$http.get("api/products/get-products").then(function(response){
products = response.data;
});
return {
products: products
}
});
If you inject the factory into your productCTRL it will have direct access to the products data:
appController("productCTRL", function(productService){
$scope.products = productService.products;
});
The controller will still be instantiated twice, but it's only going to reassign the $scope.products variable. Most importantly, it no longer makes a redundant call over HTTP to your API.

It looks like you want to access the scope of other controllers-
Three are ways to communicate-
Parent child inherited scope - You have to create a parent controller and child controller can communicate using Parent scope. It is recommended only for tightly coupled controllers.
Event bus - Subscribe can listen on event ($on) and publisher can publish using $emit (current to parent scope can access using $on) or $brodcast (current to child scope can access using $on). Sibling controller can't share the data using this approach. To share the data between sibling controller, you can use $rootScope.broadcast and this event will be listen by all scope including sibling controllers. It is also coupled using event.
Using service - Both controller can share the data using common service because services are singleton.

Related

How to preserve scope data when changing states with ui-router?

I'm building a single page web app using AngularJS with ui-router. I have two different states, one parent and one child. In the parent state, 'spots', users can make a selection from an ng-repeat and their selection is shown using the scope.
When a user makes the selection, I have ng-click fire a function which uses $state.go to load the child state 'details'. I would like to load their selection in the child state, but it appears that the scope data is gone?
I've tried using the same controller for each state. ui-sref doesn't work either.
From the parent state HTML template
<div class="card-column mx-0" data-ng-click="makeSelection = true">
<div class="card mx-0 mb-3 ng-scope" data-ng-click="showSpot(spot);" data-ng-repeat="spot in spots | filter:{'game':gameID} | filter:{'walking':distanceID} | filter:{'vehicle':vehicleID} | orderBy:'price' | filter as results">
<div class="row no-gutters">
<div class="col-sm-12 col-md-3 col-lg-3">
<img src="{{ spot.image }}" alt="parking spot"/>
</div>
<div class="col-sm-12 col-md-9 col-lg-9">
<div class="card-body px-4 pt-4">
<h6 class="text-small-extra text-muted font-weight-normal text-uppercase"><span style="letter-spacing: .05rem;">{{ spot.type }}</span></h6>
<h5 class="card-title">{{ spot.address }}</h5>
<h4 class="text-muted float-md-right">${{ spot.price }}<span style="font-size: 1rem; font-weight: 400">/day</span></h4>
</div>
</div>
</div>
</div>
Snippet from the controller
$scope.showDetails = function() {
$state.go('spots.details'); //my route...
}
$scope.showSpot = function(spot) {
$scope.spot = spot;
$scope.showDetails();
}
Snippet from app.js
.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/")
$stateProvider
.state('spots',{
url: '/',
templateUrl: "/parkit/master/spots-available.html",
controller: 'parkitController'
})
.state('details', {
parent: 'spots',
url: '/details',
templateUrl: '/parkit/master/details.html',
})
.state('statetwo', {
url: '/statetwo',
template: '<h1>State Two</h1>',
controller: 'parkitController'
});
})
I expected the user selection to show on the child state after ng-click is fired.
You need to under stand how prototypal inheritance works. When a parent puts a property value on the scope with
$scope.value = 'something';
In a child component if you access $scope.value the inheritance chain will find $scope.value.
If the child sets
$scope.otherValue = 'something';
If follows the inheritance chain, doesn't find a value of otherValue and creates a property on the child scope, not the inherited prototype so the parent component and any other children of the parent do not see it.
You can use what is called the dot rule of prototypal inheritance. If the parent creates an object on the scope called something like data
$scope.data = { value: 'something' };
Now if the child puts a property on the data object
$scope.data.otherValue = 'something';
It looks for the data object, finds it in the inheritence chain and because you are adding a property to an instance of an object it is visible to the parent and any children of the parent.
let parent = {
value: 'some value',
data: { value: 'some value' }
};
let child = Object.create(parent);
console.log(child.value); // Finds value on the prototype chain
child.newValue = 'new value'; // Does not affect the parent
console.log(parent.newValue);
child.data.newValue = 'new value'; // newValue is visible to the parent
console.log(parent.data.newValue);
Short answer is to just never inject $scope and use controllerAs syntax.
To share data between controllers you use a service that is injected to both controllers. You have the spots collection on the service and use a route param to identify which spot the other controller should use or have a place on the service called currentSpot set by the other controller.
Services are a singleton object that you create at the module level and then all controllers that ask for them in their dependency list get the same instance. They are the preferred way to share data between controllers, $scope hierarchies are bound to lead to confusion as the prototypal inheritance nature of them can be confusing. A child $scope is prototypally inherited from it's parent, this seems like you should be sharing data but when a child controller sets a property it is not visible to the parent.
You are learning an outdated way of Angular programming. Injecting $scope is no longer a recommended way. Look at using components. Components are a wrapper for a controller with an isolated scope and using contollerAs syntax. Isolated scopes make it much cleaner to know where data comes from.
Take a look at my answer on this question
Trying to activate a checkbox from a controller that lives in another controller

Changing $scope variable value in controller not changing value on view

I am working on Cordova tool and angularjs for my application.
cordovaApp.controller("VacationCtrl", function ($scope, $http, $location) {
$scope.tempdate = "2222";
$scope.ruleDetails = function () {
$scope.tempdate = "3333";
}
});
view 1
<div ng-controller="VacationCtrl">
<a ng-repeat="data in rules" ng-click="ruleDetails()" class="summaryListBorder" href="#detailVacationRule">
</a>
</div>
view 2
<div ng-controller="VacationCtrl">
{{tempdate}}
</div>
In above given code, I sat value of $scope.tempdate to "2222". When I am click on link, it calls ruleDetails() and set $scope.tempdata = "3333". But when the new page is open with ng-view, it shows only old value, i.e. "2222". I want to change it with "3333". I have tried with $scope.$apply() too.
Thanks.
Every ng-controller attribute creates a new instance of the controller, which won't share the same scope as other instances. You want to wrap both divs in a single controller instance, like:
<div ng-controller="VacationCtrl">
<div>
<a ng-click="ruleDetails()" href="#detailVacationRule">
</a>
</div>
<div>
{{ tempdate }}
</div>
</div>
If you need separate controllers, then you want to move common functions/fields into a service, which operates as a singleton so you can use it to share information between controllers. Or you could contain the separate controller instances in a parent controller, which will hold common fields and can be accessed through each controller's scope.

how to use angular's ng-repeat filter with input outside the controller?

I want to use the angular's ng-repeat filter like so:
<div ng-repeat="trade in trades | filter:searchTrades | orderBy:predicate:reverse">
the problem here is the input control where I want to bind "searchTrades" to exists OUTSIDE the controller and view where the ng-repeate exists. the input field exists outside the controller for a good reason. it's a global search input that i intend to use differently with each controller. so further more I will need to give the search input different behavior depending on which controller/view is active.
This is a question of scopes, and eventing between scopes. As angular uses prototype inheritance, you can still gain access to "parent" scope properties and react to them.
The short of it, if you have searchTrades on a parent controller, the child controller can access it. Note if the child controller modifies searchTrades it will make a "new copy", if you need to do that use $scope.$emit and $scope.$on
Here is a plunker to look at
Consider the following
Controllers
function MainCtrl($scope, ...) {
$scope.search = 'My search term'
}
function ChildCtrl1($scope, ...) {
$scope.items = ['Foo', ... ]
}
View
<body ng-controller="MainCtrl">
<label>Search</label> <input ng-model="search" />
<div ng-controller="ChildCtrl1">
<ul>
<li ng-repeat="item in items | filter:search">{{item}}</li>
</ul>
</div>
</body>
ChildCtrl1 will inherit search from the parent controller, and it can be used as "normal"

get ng-click, on injected element, to change the class of an element already in the view?

I have a <ul> that gets populated with the server. But in that controller there is also an iframe. When the <li>'s arrive there is some disconnect between them and the iframe even though they are in the same controller.
When you click one of the li's it should change the class on the iframe but it's not. However, If I move the iframe inside of the ng-repeat that injects the iframe it works.
View
<div class="content" ng-controller="FeedListCtrl">
<ul>
<li ng-repeat="item in items">
<div data-link="{{item.link}}" ng-click="articleShowHide='fade-in'">
<div ng-bind-html="item.title" style="font-weight:bold;"></div>
<div ng-bind-html="item.description"></div>
<!-- it works if i put the iframe here -->
</div>
</li>
</ul>
<!-- doesn't work when the iframe is here -->
<iframe id="article" ng-class="articleShowHide" src=""></iframe>
</div>
Here is the controller. It does an ajax call to get the data for each <li>
Controller
readerApp.controller('FeedListCtrl', ["$scope", "$http", "FeedListUpdate", function ($scope, $http, FeedListUpdate) {
$scope.setFeed = function (url) {
$http.get('feed?id=' + FeedListUpdate.GetCurrentFeedUrl()).success(function (data) {
$scope.items = data.currentFeed.items;
});
};
}]);
When inside of an ng-repeat you are in a different scope which means you are not setting the variable you think you are. Use $parent and that should work. The syntax is:
<div data-link="{{item.link}}" ng-click="$parent.articleShowHide='fade-in'">
Side note for others finding this - sometimes adding curly brackets helps as well. For more information on ng-class see here: http://docs.angularjs.org/api/ng/directive/ngClass
An Example
In case anyone wants to see this in action, I put together an example demonstrating a few ways to set the class as well as demonstrating the issue in scope (See: http://plnkr.co/edit/8gyZGzESWyi2aCL4mC9A?p=preview). It isn't very pretty but it should be pretty clear what is going on. By the way, the reason that methods work in this example is that the scope doesn't automatically redefine them the way it does variables so it is calling the method in the root scope rather than setting a variable in the repeater scope.
Best of luck!

AngularJS: share the data of the same controller in two different, not nested parts of the page

I want to share the data of one controller between two places in my page. For example:
<div ng-app="myApp">
<div ng-controller="myController">
<input type="text" ng-model="x" /> {{x}}
</div>
</div>
<!-- these are in totally different places and I do not want, nor can't nest them -->
<div ng-app="myApp">
<div ng-controller="myController">
<input type="text" ng-model="x" /> {{x}}
</div>
</div>
and of course, this:
var myApp = angular.module('myApp', []);
myApp.controller('myController', function($scope) {
$scope.x = 'test';
});
What can I do so that, no matter what I type first input text will be reflected in the second? And vice versa? Basically the same data being propagated to these two sections, while maintaining a single copy of the data.
JSFiddle here: http://jsfiddle.net/LETAd/
PS: I bootstrap it manually, if this is of any relevance.
Thanks!
To share data between controllers, normally a service is your best option. Put the shared data into the service, and inject the service into the controller:
function myController($scope, MyService) {
Each scope/controller instance will then be able to access the shared data.
Note that services are singletons, so there will only be one instance of your shared data around.
Here is a fiddle (I didn't write it) showing how two controllers can share data.
See also AngularJS: How can I pass variables between controllers? and Angularjs: two way data bindings and controller reload.
Ideally, you should have only one application running in a single page. Since you also need to communicate between the controllers, you should really run a single application. Possibly on body or html. Then you can create a main controller which would encapsulate all your controllers. (controller inheritance).
Here is what it should look like:
<html ng-app="myApp">
<head>...</head>
<body ng-controller="MainCtrl">
<div ng-controller="MyCtrl">
<input type="text" ng-model="mainView.x" /> {{x}}
</div>
<div ng-controller="MyCtrl">
<input type="text" ng-model="mainView.x" /> {{x}}
</div>
</body>
And JS:
function MainCtrl($scope) {
$scope.mainView = {};
}
function MyCtrl($scope) {
}
We created a mainView object on the MainController, and since MyController and its scope prototypally inherit from MainController we can reach that.
There is one caveat you should be aware of, when you use ngModel, it is almost always best to have a dot in somewhere (paraphrased from angularjs's authors).
Due to javascript's prototypal inheritance:
// In MainCtrl
$scope.mainView.x = "hello";
$scope.myX = "hello";
// In MyCtrl
$scope.mainView.x
>> "hello"
$scope.myX
>> "hello"
$scope.mainView.x = "welcome";
$scope.myX = "welcome";
// In MainCtrl
$scope.mainView.x
>> "welcome"
$scope.myX
>> "hello"
When you ask for a property in an object in javascript, it looks its properties to see if there is one, if not, it goes up in the prototype chain (parent), and looks for it there, it goes up until it finds any or goes at the end of the prototype chain.
So when we set $scope.myX, we don't actually change myX in the parent scope but we create a property called myX in the current scope; because of the hiearchy in the prototype. However, when we set $scope.mainView.x, we first ask for mainView then set x which then results in changing the value in parent scope.
I know it feels kind of unrelated to the original question but surely one would suffer from this when one goes into controller and scope inheritance.

Resources