Mimicking collection changes in ng-repeat - javascript

I have an AngularJS app. My app has the following JSON array defined on the scope when the controller is initialized:
$scope.orders= [
{ isSelected:false, quantity:0, price:1.33, name:'', description:'' },
{ isSelected:false, quantity:0, price:2.54, name:'', description:'' }
];
...
var addOrder = function() {
$scope.orders.push({ isSelected:false, quantity:0, price:getPrice(), name:getName(), description:getDescription() });
};
I'm displaying the orders like this:
<ul class="list">
<li ng-repeat="order in orders">
<div>
<h2>{{order.name}}</h2>
<p>{{order.description}}</p>
</div>
</li>
</ul>
Whenever addOrder gets called, a object gets added to the order collection. However, that change doesn't get reflected in the UI. How do I get the change to reflect in the UI?
Thanks

$scope.$apply() is required to update the scope. When you use Angular THINGS like directive, ng-* events, $timeout, $http, etc they come pre-wrapped with this function. When you work outside of Angular's world you'll need to start telling it when you update the scope.
Here's a good rundown on it all: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
Be careful about simply sticking it within generic functions - you'll run into problems (calling $apply within $apply) if they are called by Angular THINGS as well as vanilla JS events. Associate the $apply() with what requires it:
.directive( 'myDir', [ function() {
return {
link: function( scope ) {
( scope.myFunc = function() {
// scope.$apply(); would cause error
})();
angular.element( window ).bind( 'resize', function(){ scope.$apply( scope.myFunc )});
}
}
}]);

Related

Is there any way to require a controller in a component that is not from another directive or component?

Can I, from a directive or component, require a controller that is not from another directive or component?
Here is what I have tried:
Attempt 1
Throws Injector Error.
angular.module('test', [])
.controller('mainController', function(){
this.someData = [{
someKey : Math.random()*10
}];
this.someFunction = function(data){
this.someData.push(data);
}
})
.component('master', {
require:{
main:'mainController'
},
controller:function(){
this.$onInit = function(){
console.log(this);
}
}
});
Attempt 2
Creates a new copy of the controller – not what I need.
angular.module('test', [])
.controller('mainController', function(){
this.someData = [{
someKey : Math.random()*10
}];
this.someFunction = function(data){
this.someData.push(data);
}
})
.component('master', {
controller:function($controller){
this.$onInit = function(){
this.main = $controller('mainController');
console.log(this);
}
}
});
To see what I mean in the second example, please see This Plunkr.
I doubt there is a way, but if I'm honest I've never fully looked into how angular does what it does. Odds are you have to create a new component/directive and you can just include its controller from there, but I was hopeful!
ng-controller is a directive. So regarding the abilities, the nearest controller can be required with
require: '^ngController'
It is not necessarily main controller, just some controller.
And regarding 'best practices', it can't, this smells bad. The code above needs no main controller at all. If there should be some global someData and someFunction - make them a service (or two). It can be injected to any component controller then, disregarding their places in DOM hierarchy.

Angular 1.5 Nested Component Bind parent Value

I am new to angularjs. I am trying angular 1.5 nested component. Can I bind parent component property in child component.
Ex:
<div ng-app='cbsApp' ng-controller='cbsCnt as ct'>
<cbs-cus-comp com-bind='ct.name'>
<child child-com-bind='cbsCusCompCntAs.name'></child>
</cbs-cus-comp>
</div>
I can get ct.name value in com-bind. But can't get cbsCusCompCntAs.name in child-com-bind. (cbsCusCompCntAs is cbs-cus-comp controller)
Working Plunker : https://plnkr.co/edit/axQwTn?p=preview
Thanks in advance.
In your first case you are referring directly to the controller scope via controllerAs.
When using components in angular 1.5 you can get hold of your parent component via require which will make parent's properties available after $onInit as per Components Documentation:
Note that the required controllers will not be available during the
instantiation of the controller, but they are guaranteed to be
available just before the $onInit method is executed!
In your specific case you can update the child component to require the parent:
var child = {
require : {parentComp:'^cbsCusComp'},
template : 'Child : <b{{cbsCusChildCompCntAs.childComBind}}</b>',
controller : cbsCusChildCompCnt,
controllerAs: 'cbsCusChildCompCntAs'
};
and its controller to get the data you need (I used the same names as you just to see it work):
function cbsCusChildCompCnt(){
this.$onInit = function() {
this.childComBind = this.parentComp.name;
};
}
Updated plunker is here.
Wow... what a wonderful example...
Took me a while to analyse it... so, I wrote my own (I think a bit more readable) version.
I really do not know how to work with Plunker... so here's the code...
Extract from my index.html file
<div ng-controller='appCtrl as ctrl'>
<parent bind-id='ctrl.name'>
<child bind-toid='parentCtrlAs.name'></child>
</parent>
</div>
The .js file
(function () {
'use strict';
var
parentComponent =
{
bindings :
{
bindId:'='
},
controller : parentCtrl,
controllerAs: 'parentCtrlAs',
restrict : 'A',
transclude : true,
templateUrl : 'parent.html',
};
var
childComponent =
{
controller : childCtrl,
controllerAs: 'childCtrlAs',
restrict : 'A',
require :
{
myParent :'^parent'
},
templateUrl : 'child.html',
};
angular
.module('app', [])
.controller('appCtrl' , appCtrl)
.component('parent' , parentComponent)
.component('child' , childComponent);
function appCtrl(){
this.name = 'Main..';
}
function childCtrl(){
this.$onInit = function() {
this.bindToid = this.myParent.name;
};
}
function parentCtrl(){
this.name = 'Parent Component';
}
})();
Hope it helps,
Regards,
Johnny
Although using the "require" parameter works, it creates a tightly bound relationship between the component acting as a child, which uses the "require" parameter, and the component acting as a parent, which consumes the child functionality.
A better solution is to use component communication as shown here.
Basically, you define a binding function in the child component definition, like so,
angular.module('app').component('componentName', {
templateUrl: 'my-template.html',
bindings: {
myFunction: '&'
},
controller: function() { // Do something here}
});
Then, in the parent markup you provide a function to call,
Parent HTML
<user-list select-user="$ctrl.selectUser(user)">
</user-list>
Finally, in the parent controller, provide an implementation of the selectUser function.
Here's a working Plunk.

Angular JS callback after ng-repeat is completed dynamically

There are tons of posts showing how to have callback functions based on a directive so that when an ng-repeat function is finished you can wait and then call a function. For example here is my example.
<div ng-repeat="Object in Objects" class="objectClass" on-finish-render>{{Object.Overlay}</div>
Then when it is completed the following calls my function
.directive('onFinishRender', function ($timeout) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last === true) {
$timeout(function () {
scope.$emit('ngRepeatFinished');
}, 0);
}
}
}
});
This successfully calls my function below when it is completed
$scope.$on('ngRepeatFinished', function(ngRepeatFinishedEvent) {
//my code goes here
}
Now all of that works perfectly and as intended the first time I set my $scope.Objects variable. My code will wait until all objects are fully rendered and then runs, literally perfect. However if after the initial everything I change $scope.Objects again, my function will still run but it will NOT wait for the completion. Its actually visible with console logs, the first go round it will pause for about half a second after I go into the directive but before the actual emit, but on subsequent changes to my ng-repeat it does not pause and simply calls my function before the dom is finished rendering.
This is super annoying and any help would be great!
One thing you should understand about ng-repeat is that it reuses DOM elements whenever possible, so if you have two objects in your repeater and you add a third, the first two elements will not be re-rendered. Only the third will be rendered, thus your directive's link function will only be called once for the newly-added object.
Similarly, if you remove an item, your directive's link function will not be run again.
Observe the behavior in this JSFiddle.
Angular’s $emit, $broadcast and $on fall under the common “publish/subscribe” design pattern, or can do, in which you’d publish an event and subscribe/unsubscribe to it somewhere else. The Angular event system is brilliant, it makes things flawless and easy to do (as you’d expect!) but the concept behind it isn’t so simple to master and you can often be left wondering why things don’t work as you thought they might.
Using $scope.$emit will fire an event up the $scope. Using
$scope.$broadcast will fire an event down the $scope. Using $scope.$on
is how we listen for these events.
(reference)
I have provided two solution according to your problem.
Solution One
<div ng-controller="MyCtrl">
<div ng-repeat="Object in Objects" class="objectClass" on-finish-render isolated-expression-foo="updateItem(item,temp)">{{Object|json}</div>
</div>
var app = angular.module('app', []);
app.directive('onFinishRender', function () {
return {
restrict: 'A',
scope: {
isolatedExpressionFoo: '&'
},
link: function (scope, element, attr) {
if (scope.$parent.$last) {
scope.isolatedExpressionFoo({ temp: "some value" });
}
}
};
});
app.controller('MyCtrl', ['$scope', function ($scope) {
$scope.Objects = [{ id: 1, value: "test" }, { id: 2, value: "TEst2" }];
$scope.updateItem = function (item, temp) {
console.log("Item param " + item.id);
console.log("temp param " + temp);
}
}]);
Solution Two
<div ng-controller="MyCtrl">
<div ng-repeat="Object in Objects" class="objectClass" on-finish-render>{{Object|json}</div>
</div>
var app = angular.module('app', []);
app.directive('onFinishRender', function ($rootScope) {
return {
restrict: 'A',
link: function (scope, element, attr) {
if (scope.$last) {
$rootScope.$broadcast("ngRepeatFinished", { temp: "some value" });
}
}
};
});
app.controller('MyCtrl', ['$scope', function ($scope) {
$scope.Objects = [{ id: 1, value: "test" }, { id: 2, value: "TEst2" }];
$scope.$on('ngRepeatFinished', function (temp) {
console.log("Item param " + temp);
});
}]);

Sharing scope in different views with custom controllers

I've got the next problem and I'd like to find a solution or if I should even be doing this like I am.
I have the following ui-router configuration:
$stateProvider.state('ListCounterparties', {
url:"/counterparties",
data: { NavMenuItem : 'Counterparties / Desks' },
views: {
'' : {
templateUrl:"./app/module_Counterparties/templates/ListCounterparties.html",
controller:"CounterpartiesControllerCtrl"
},
'deskLists#ListCounterparties' : {
templateUrl : './app/module_Counterparties/templates/DesksDualListTemplate.html',
controller:'DesksControllerCtrl'
}
}
The first, unnamed view, has a table from which I can select a row and then a method will be called to populate a dual list from the second view.
Up until now, I've had both in the same controller, but the controller is getting too big and I decided I had to separate them.
The method to populate the dual lists in 'deskLists#ListCounterparties' is defined in DesksControllerCtrl but it should be called in CounterpartiesControllerCtrl, as the event of row selection is in that controller.
The problem is that the scopes are not shared and the method is inaccesible to the unnamed view.
Accessing the scope in DesksControllerCtrl I could see that accessing the $parent property twice I can get to the CounterpartiesControllerCtrl, but I don't thin that's an ideal thing to do.
Thanks in advance.
Sharing data, methods, etc. between multiple controllers the Angular way would be to create service(s). That means, you create e.g. a service which holds all your data and another one which provides functionality for several controllers. Then, just inject them in your controllers:
var myApp = angular.module('myApp', []);
myApp.factory('myGlobalData', function() {
return {
data: 1
};
});
myApp.factory('myService', function(myGlobalData) {
return {
increment: function() {
myGlobalData.data++;
}
};
});
myApp.controller('MyCtrlA', function($scope, myGlobalData, myService) {
$scope.myGlobalData = myGlobalData;
$scope.myService = myService;
});
myApp.controller('MyCtrlB', function($scope, myGlobalData, myService) {
$scope.myGlobalData = myGlobalData;
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<p ng-controller="MyCtrlA">
{{myGlobalData.data}}
</p>
<p ng-controller="MyCtrlB">
{{myGlobalData.data}}
</p>
<div ng-controller="MyCtrlA">
<button ng-click="myService.increment()">Increment data in myGlobalData service</button>
</div>
</div>

AngularJS data binding between service and controllers

I would like to have clarifications about data-binding with AngularJS.
I wish to have an array in service and share it with multiple controllers.
I would like , when a controller modify data in service, that the modification was take in account in others controllers et theirs html templates was update automatically.
Unfortunately, I succeed a little, but not with an angular manner because I use for this the Observer pattern or angular.copy..
When I try with a pure angular way, it does not work.
Here an example of code:
The service that contains datas to shared
app.factory( "MyService", [
function () {
myTableToShare = [];
return {
myTableToShared : myTableToShare,
};
}
] );
The controller that modify data of service
app.controller( 'AddCtrl', [
'$scope', 'MyService', function ( $scope, MyService ) {
$scope.myService = MyService;
$scope.addElementToSharedTable = function( element ) {
$scope.myService.myTableToShare.push( element );
};
}] );
The controller that permit to print datas of table into template
app.controller( 'ReadCtrl', [
'$scope', 'MyService', function ( $scope, MyService ) {
$scope.myService = MyService;
}] );
**The template that print elements of table **
<div ng-controller="ReadCtrl">
<ul >
<li ng-repeat="element in myService.myTableToShare">
<span>{{element.id}} - {{element.name}}</span>
</li>
</ul> </div>
I have apply this method thanks to differents HOWTO. unfortunately these HOWTO did not use an array object but a String. Is it a path to follow?
In waiting I apply the pattern observer et I notify events from controller to services but it seems to me that its not a good solution because it too verbose.
Thaks
use app.value(...) if controllers are in the same module 'app'.

Categories

Resources