Is there an angular way of checking if a model has changed since it's initial state when the controller was loaded ?
I can of course create a variable to store the initial state and compare against it, but is there a more elegant way ?
function myController (Service) {
var vm = this;
vm.model = Service.getValue(); // Service sets inital value at controller load
vm.method = function(){
// do something here only if vm.model changed form inital State
}
}
You can use $watch on the scope
$scope.$watch('vm.model', function (newVal, oldVal) {
If (newVal) doSomething();
}, true);
The optional Boolean for the second parameter allows you to do a deep watch of the variable properties.
Make sure that you inject $scope into your controller too
you should use $watch to tracking all change of variable.
View more detail on here: https://docs.angularjs.org/api/ng/type/$rootScope.Scope
Related
how can i save data from 1 view to another in angularjs?
i did $rootScope
From what I see, you use 2 different controllers for each view (or one for the view and none for the root view).
The problem is that Angular can't share data between controllers like that.
You either have to use a service/factory, or use the rootscope, but not as you did, rather with broadcast and emit
If I were you I would use a service.
EDIT Here you go, a service for you :
(function() {
'use strict';
angular
.module('YourModuleName')
.factory('CountriesService', CountriesService);
CountriesService.$inject = ['Your', 'dependencies', 'here', 'in', 'string'];
/* #ngInject */
function CountriesService(your, dependencies, here, not, in, string) {
var service = {
setCountries: setCountries,
getCountries: getCountries
};
var vm = this;
vm.countries = []; // Or maybe an object ?
// ... List of other variables you need to store.
return service;
////////////////
function setCountries(listOfCountries) {
vm.countries = listOfCountries;
}
function getCountries() {
return vm.countries;
}
}
})();
This will store your variables. In your controller you add CountriesService as a dependency, to save you use CountriesService.setCountries and to load you use CountriesService.getCountries. Be aware that refreshing the page will delete all the data !
EDIT NUMBER 2
If you're scared of John papa guidelines, here is a simple service you can use in the same file you put your controller :
app.factory('CountryControl', function(your, dependencies) {
var service = {
setCountries: setCountries,
getCountries: getCountries
};
this.countries = []; // Or maybe an object ?
// ... List of other variables you need to store.
return service;
////////////////
function setCountries(listOfCountries) {
this.countries = listOfCountries;
}
function getCountries() {
return this.countries;
}
});
I have an app that does this more or less. A service fixes this nicely AND creates a mechanism such that you can do this anywhere in your app.
First, I would recommend not trying to manage this with scope. Just put an object on your controller (myFormObj), and add the properties you want to it (name, rank, serialnumber, etc).
Then bind the input fields of the form, to the properties in that object (as opposed to scope vars). So your ng-model things would look like myCtl.formObj.name, and so on.
When the user triggers the event that changes the view, save a COPY (angular.copy) of that formObj off to the side, usually in a Service (think FormStateService or something). FormStateService could do nothing more than hold a simple array.
this.forms = { 'TheNameOfYourForm' : theFormObjToSave };
So, when the user triggers that event that leaves the form, you just do this:
formStateSvc.forms [ 'NameOfMyForm' ] = angular.copy ( theFormObj );
When the user comes back to the original view and the controller initializes, you just ask the formStateSvc:
if ( 'NameOfMyForm' in formStateSvc.forms ) {
this.formObj = formStateSvc.forms [ 'NameOfMyForm' ];
}
Voila, your old form state is restored.
More robustly, you could create "addForm, removeForm" methods etc, you could ensure against things like undefined, and you could make the rebind to the former state implicit (when your form's controller inits, just ask it to restore the state if there's any to restore). So your controller would just have:
this.formObj = formStateSvc.rebindOldDataIfItExists ( 'MyFormName' );
You get the idea.
A simple approach is to create a value provider object and publish it on scope:
//Create value provider object
app.value("FormObj", {});
app.controller("myController", function($scope, FormObj) {
//Publish on scope
$scope.FormObj = FormObj;
});
Then have the ng-model directives use that object:
Name <input ng-model="FormObj.name"><br>
Rank <input ng-model="FormObj.rank"><br>
SerialNum <input ng-model="FormObj.ssnum"><br>
The value object is a singleton which persists for the life of the application. Changes to the contents of the object will be retained and available to other controllers and will survive changes to the view.
The DEMO on PLNKR
I have a navbar directive which sits above ng-view. It utilises the $rootScope to trigger events to show buttons in certain views.
I am trying to add a button to the directive template which will switch a boolean in a controller for a particular view. The view shows a period of time and each period has a particular boolean that I want to switch from the directive.
The boolean value is saved in a local storage object which is initialized when each iteration of this particular view is loaded.
First, the value needs to be communicated to the directive so the button can display as being set to true or false. When the switch is toggled, the value of that boolean needs to make its way from the directive, through the $rootScope, to the controller and then be saved in the storage object.
When the view is changed, the whole process needs to repeat. The switch needs to be able to be switched on and off multiple times, obviously.
At present, I am emitting the value from the controller to the $rootScope and then listening for that value in the directive link function.
However, what is the best way to get that $rootScope value BACK into the controller. I tried setting up a $rootScope.$watch in the controller which appeared to work on any single page but when navigating between different time periods, the $rootScope value of the boolean was not resetting properly.
I tried resetting the value in the controller initialization as follows:
$rootScope.booleanValue = false;
but this didn't work.
I have also tried the following:
$scope.$on('$routeChangeSuccess', function (next, current) {
$rootScope.booleanValue = false;
});
but I can't get the whole cycle to work properly. It still seems as though the value of the property in the $rootScope is not resetting from the view before and is then carrying over when an adjacent pay period view is loaded.
I hope this makes sense. I will save you from too much code as I think the basic idea is here.
What you are trying to do is share state from your navbar directive (an isolate scope) and your view's controller. I recommend you use a factory provider service to share that state:
angular.module('myApp').factory('navbarState', function (){
return {started: false}
});
In your navbar directive, inject the service and store the state in that service:
angular.module('myApp').directive('navigationBar', [
'$rootScope',
'navbarState',
//'NavigationStackService',
//'NavigationBarService',
function ($rootScope, navbarState) {
function link(scope, element) {
scope.startEditMode = function(){
console.log("Edit clicked");
navbarState.started=true;
//NavigationBarService.hideNavigationEdit();
//NavigationBarService.showNavigationDone();
};
scope.finishEditMode = function(){
console.log("Done clicked");
navbarState.started=false;
//NavigationBarService.hideNavigationDone();
//NavigationBarService.showNavigationEdit();
};
}
return {
templateUrl: 'templates/navigation-bar.html',
restrict: 'E',
scope: {},
link: link
};
}
]);
In your view controller, retrieve the service, put it on the controller's scope, and use it in your template.
angular.module('myApp').controller('controller2', function(navbarState) {
console.log("view controller2 started");
var vm = this;
vm.navState = navbarState;
vm.message = "hello from ct2";
});
The DEMO on JSFiddle.
Do I need to create a getCurrent to keep bindings (if I have {{current.status}} in my view ex)? Or would current: current be enough?
Would I loose the binding to status if Id do $scope.status = services.status. Meaning status wont be updated in a view if its changed.
Will bindings to someValue be kept? Meaning it will change in my view if its changed in the service if I do $scope.someValue = services.someValue
function someService() {
var current = {
status: ''
};
var someValue = 'hello';
//////////
var service = {
current: current,
getCurrent: getCurrent,
status: current.status,
someValue: someValue
};
return service;
//////////
function getCurrent() {
return current;
}
}
No, you don't need the getCurrent method, current:current should be enough.
2 & 3. No, because you are setting the scope variables to the service's properties ($scope.status = service.status) and those properties are strings, the bindings will NOT be kept. However, if you instead assign the entire service object as the scope variable and use dot notation in your bindings, then they will because you will be updating the object whose reference was injected into the controller (the service). The important things to note are in order to understand why your method does not work, but the alternative does is to understand that
objects are passed by reference
for the above reason, angular recommends that your bindings always use dot notation.
This is what your code would could look like to make it work:
//service
.factory('service', function() {
var current = {
status: 'theStatus'
};
var someValue = 'hello';
var service = {
current: current,
status: current.status,
someValue: someValue
};
return service;
})
// controller
.controller('theCtrl', ['$scope', 'service', function($scope, service) {
$scope.serviceData = service;
}])
// view
<p>{{serviceData.current}}</p>
<p>{{serviceData.status}}</p>
<p>{{serviceData.someValue }}</p>
And here is the sample plunker: http://plnkr.co/edit/n2P07mjwnMVHCl4l7SAj?p=preview . Note that it has 2 examples, the first one shows your method and the second one shows the object method.
EDIT - BIG CAVEAT:
One big caveat to notice is that in your service, if the someValue or the current variables change, your view will NOT be updated. Because we are returning the service object, changes WILL reflect in the service object's someValue, current and status properties, but those WILL NOT cause the original current and someValue variables to also be in sync.
Ok, as you are programming in JavaScript, you're not obliged to use getter/setter as you would in Java for example.
All angular service are singleton, so you can easily share some data. Moreover by creating Factory, you'll be able to return an object, with want you want inside, for example method, that will be invoked. You can make the connection with the factory pattern.
In your case, you can save your service instance into your current $scope.
EDIT
In your factory, you should return your current object. Then, you should use it in your view to retrieve current status. So, you will get an object, not just a fixed value, so it will updated.
Controller
(function(){
function Controller($scope, Service) {
//Register the Service instance into our scope
$scope.service = Service;
//Retrieve current object with status property
$scope.working = Service.current;
//Retrieve VALUE of current object
$scope.not_working = Service.status;
$scope.changeStatus = function() {
Service.changeStatus('another status');
}
}
angular
.module('app', [])
.controller('ctrl', Controller);
})();
Service
(function(){
function Service($timeout) {
var current = {
status: 'off'
};
var someValue = 'hello';
$timeout(function() {
//Update service status
current.status = 'on';
}, 500);
//////////
var service = {
//Return the current object
current: current,
//Just set the VALUE of current.status
status: current.status,
getCurrent: getCurrent,
someValue: someValue,
changeStatus: changeStatus
};
return service;
function getCurrent() {
return current;
}
function changeStatus(status) {
alert("status changed");
//Modifying status property of current object
current.status = status;
}
}
angular
.module('app')
.factory('Service', Service);
})();
HTML
<body ng-app='app' ng-controller='ctrl'>
Status : {{working.status}}<br>
Status not updating : {{not_working}}<br>
SomeValue : {{service.someValue}}
<br>
<button ng-click="changeStatus()">go</button>
</body>
You can see the Working Plunker
Angular will keep track of changes on $scope with it's implementation of dirty checking. So, when an event occurs within your Angular application and an $apply or $digest is invoked, Angular will iterate through all of the $watch values and update any bound values accordingly.
Without the newer controllerAs syntax, you would want to put any values that you want bound onto the $scope object. Then, any events fired within your Angular application will trigger your updates automatically.
Here is a simple demo binding $scope.status.value to three DOM references:
http://codepen.io/anon/pen/KdKrqe
To answer your questions directly:
No, you don't need to create getters/setters for your bound values due to Angular dirty checking. But, current: current is not enough. That would be reassigning a value outside of Angular to its $scope and depending on the Object type, this will be assigned by either value or reference. Any 'connection' to the original value will be lost when you assign by value (Numbers, Strings, Booleans...)
Yes, you would loose your binding if service.service is assigned by value i.e. You wouldn't be able to update services.status to update the value of $scope.status. If you changed it a bit to: $scope.services = services; Then $scope.services.status would be bound.
If you used the suggestion from above, any changes would be reflected in your original object: $scope.services = services; then, any changes on services. would be reflected in your original object.
I think the two main points here are understanding assignment by value/reference in JS and how Angular implements bound values by dirty checking.
So I know with two way binding, =, in a directive a controller's value can be passed into the directive. But how do you pass a change in the isolated directive back to the controller?
So for example, I have a form that has a slider built with a directive with an isolated scope. Initial value is set from controller, then changes in directive with isolate scope.
What I'm trying to do is change the controller's value when the directive variable with two way binding changes as well.
Any suggestions?
You have two possible ways of achieving this. One is creating a watch statement in the controller on the variable that you passed into the directives isolate scope.
// code in view controller
$scope.sliderValue = 0;
$scope.$watch('sliderValue', function(newValue) {
if (angular.isDefined(newValue)) {
// Do stuff with new slider value
}
});
Note that we need the isDefined, because every watch fires on scope compilation with the initial value being undefined.
The other way is enhancing your directive with a parameter which is evaluated as your slider value changes (much like a callback function).
// sample directive code
angular.module('my-ui-controles', []).directive('mySlider', [function() {
return {
template: '...',
scope: {
value: '=mySlider',
onChange: '&'
},
link: function(scope, elem, attrs) {
// assume this is called when the slider value changes
scope.changeValue = function(newValue) {
// do other internal stuff and notify the outside world
scope.onChange({value: newValue});
}
}
}
}])
And now you can use this in the template likes this:
<div my-slider="sliderValue" on-change="doStuff(value)"></div>
What happens now is as soon as the slider value changes we evaluate the onChange expression that is passed into the directive. The value in doStuff is filled with your new slider value. The object that we have passed on to onChange actually is the scope with which the expression is evaluated and doStuff can be any method from your controller.
The main benefit is that you have the logic for notifying somebody in your directive rather than implicitly in your controller through a watch.
Hope that gets you in the right direction.
If you have pass any variable to isolated scope you can bind it (set any listener on it) in both sides.You can event use Angularjs Events to send any signal, if variable as been changed.
maybe this articles can help you.
http://www.w3docs.com/snippets/angularjs/bind-variable-inside-angularjs-directive-isolated-scope.html
http://www.w3docs.com/snippets/angularjs/change-variable-from-outside-of-directive.html
So recently I've been updating something that looks like the below:
$scope.arrayOfThings = [];
function setArrayOfThings() {
thingsService.get().then(function (things) {
$scope.arrayOfThings = things;
});
}
$scope.$on('arrayOfThings.add', setArrayOfThings);
To look more like this (using the lesser-known promise integration into bindings...):
$scope.arrayOfThings = thingsService.get();
But how do I force arrayOfThings to update (or re-resolve?) when the collection is changed from another $scope?
arrayOfThings can be seen only inside a child scope, so any change of arrayOfThings in a child scope will maintain data-binding anyway. The data-binding has to be resolve manually by $scope.$apply if the arrayOfThing is changed from an event (DOM event, $broadcast, etc)
You need to put a watch on the service call thingsService.get in the controller that you want to be notified of a change. Like this:
$scope.$watch(thingsService.get, function(newVal, oldVal){
$scope.arrayOfThings2 = newVal;
} );
This is assuming you are injecting that service into the new controller.