AngularJS models, multiple $scopes, and two-way data binding - angularjs

I like that AngularJS doesn't require and special syntax for Models, but there's one scenario I can't seem to wrap my head around. Take the following
My dataService wraps whatever flavor of data storage I'm using:
app.factory('dataService', function() {
var data = 'Foo Bar';
return {
getData: function() {
return data;
}
};
});
I have two controllers that both access the same piece of data:
app.controller('display', function($scope, dataService) {
$scope.data = dataService.getData();
});
app.controller('editor', function($scope, dataService) {
$scope.data = dataService.getData();
});
If I then have two views, one of which modifies the data, why doesn't the other update automatically?
<div ng-controller="display">
<p>{{data}}</p>
</div>
<div ng-controller="editor">
<input type="text" value="{{data}}"/>
</div>
I understand how this is working in something like Knockout where I'd be forced to make the data a knockout observable object. So any modifications in one part of the application trigger subscriptions and update views in another. But I'm not sure how to go about it in Angular.
Any advice?

There are few changes that I would suggest to make it work.
change the data object from a string to object type
Use ng-model to bind the input field
HTML
<div ng-controller="display">
<p>{{data.message}}</p>
</div>
<div ng-controller="editor">
<input type="text" ng-model="data.message"/>
</div>
Script
app.factory('dataService', function() {
var data = {message: 'Foo Bar'};
return {
getData: function() {
return data;
}
};
});
Demo: Fiddle

I haven't been stuck with the same situation, but the thing that jumps out at me is what you are sticking in the scope. There was an angular video where scope was discussed. You should put model objects in the scope and not use the scope as your model object.
In your example two scopes will be created each with the string data. Since data is a string and immutable, it will be replaced in scope of editor when changed. In your example if you had dataService return an object and that object is shared between the controllers, then perhaps your problems would be resolved. Try having dataService return model of {data: data} and bind to model.data instead of data.
This is untested, but should work based on how I know angular works.

Related

Why does not work two controller in Angular JS?

I have controller is named "UserController" in top of page:
<div ng-controller="UserController"><input type="text" ng-model="search"></div>
Also the same controller in bottom page from directive ng-view:
<div class="bottom" ng-controller="UserController">{{search}}</div>
Why I dont get value {{search}} in bottom part, when I fill field input in top?
Can I use one controller two times in a page?
Yes, you can use two controllers in AngularJs, Here is a demo.
What happens when I use ng-controller?
When you add ng-controller to a DOM element, angular create an instance of controller function and attaches it with that DOM, and thats why there is no two way data-binding between those divs.
How can I use data binding to share data between controllers?
You can use $rootScope variable or you can use services.
you can create service and inject in controller as dependency, so you can access its property with two way binding feature.
As said by JB Nizet, you need to have everything in the same "div".
<div ng-controller="UserController">
<input type="text" ng-model="search">
<div id="search-query">{{search}}</div>
</div>
Having the search-query at the bottom of the page is a matter of CSS, not Angular.
Controllers are not singletons. You have one controller for the top div, a second controller for the second div. One scope for the top div, one scope for the bottom div.
Both controllers have the same name, but you are ultimatally calling you controller function twice.
Some options you might want to consider to solve your problem:
Option 1) Use parent scope.
ng-model="$parent.search"
{{$parent.search}}
Option 2) Use root scope.
ng-model="$root.search"
{{$root.search}}
Option 3) Store the value in a service.
Services are singletons. If you type myService.search = $scope.search, then that value can read from the other controller.
You wont be able to watch a service variable, so perhaps you want to use the observer pattern here.
app.service("search", function() {
var listerners = [];
this.register = function(listener) {
listerners.push(listener);
};
this.update = function(searchValue) {
for(var i in listerners) {
listerners[i](searchValue);
}
};
});
app.controller("UserController", function($timeout, search){
search.register(function(searchValue) {
$timeout(function(){
$scope.search = searchValue;
});
});
$scope.$watch('search', function (newVal, oldVal, scope) {
search.update(newVal);
});
});
Option 4) Broadcast the new value.
$scope.$watch('search', function (newVal, oldVal, scope) {
$rootScope.$broadcast('search', newVal);
});
$scope.$on('search', function(event, data) {
$scope.search = data;
});
You can have multiple instances of the same controller in your page. They share the same functionality. But every instance of that controller is getting his own $scope. So in your first controller $scope.search can be 'mySearch', but the second controller won't get this, because it's another $scope.
You can do two things:
You can put the controller on a containing element, let's say the body, so both your input and your div are within the same $scope.
OR, if you want them to be seperate, you can use a service to share the search.
Your HTML:
<div ng-app="myApp">
<div ng-controller="UserController">
<input type="text" ng-model="search.mySearch"/>
</div>
<div ng-controller="UserController">
{{search.mySearch}}
</div>
</div>
Your Javascript:
var myApp = angular.module('myApp', []);
myApp.factory('Data', function(){
return { mySearch: '' };
});
myApp.controller('UserController', function( $scope, Data ){
$scope.search = Data;
});
See Fiddle

How to update scope variable using service

i have two controllers, the data in the first controller will be updated by click functions in the second controller. I am using service to communicate between controllers and not getting the expected result. whats wrong i am doing
link: http://jsbin.com/hozabigedu/1/
Well, you had several issues. First, I changed your data to be an object and to have a boolean as a property, rather than have itself be a boolean. This is good practice if you want to share info using a service. It's better that you share an object so that different controllers share the same reference. Primitives are problematic for that scenario. So here's the new service:
app.service('testservice', function(){
var data={}; //Changed here
getDataText = function() {
return data;
};
setDataText= function(val) {
data.text = val; //And changed here
return data;
};
return {
getDataText: getDataText,
setDataText: setDataText
};
});
Another issue is that your controller called the wrong function, it should be getDataText():
$scope.inputText = testservice.getDataText();
And lastly, you forgot to close the divs of the click elements, so the click 2 events bubbled up to the click 1 events thus showing right after hiding, so replace the 2nd controller div with this one:
<div ng-controller="secondController">
<div ng-click="showDiv();">click1</div>
<br/>
<br/>
<div ng-click="hideDiv();">click2</div>
</div>
</div>

AngularJs Inline Check if an array check

Inline in AngularJs is there a way to check if something is an array?
I would have thought this to work:
<div ng-show="Array.isArray(textStuff[0][1])">Hi</div>
I have verified it is in fact an array. Is there something I am missing or another way?
You can put angular.isArray on the scope...
$scope.isArray = angular.isArray;
<div ng-show="isArray(textStuff[0][1])">Hi</div>
Fiddle
You can create global filters to use in your JS or HTML to check against object types. This way you don't pollute your $rootScope or $scopes to use it everywhere, unlike the accepted answer... Angular also has some built in utility functions that can check object types:
angular
.module("yourModule")
.filter("isArray", function() {
return function(input) {
return angular.isArray(input);
};
});
In HTML:
<div ng-show="{{ textStuff[0][1]) | isArray }}">Hi</div>
You may also inject the $filter service into your Controller to access the custom filter by name and compute the filtered results when your controller instance is instantiated (and also when your data changes). This prevents performance issues due to the view expression getting computed rapidly.
angular
.module("yourModule")
.controller("MyController", MyController);
MyController.$inject = ["$filter", "$scope"];
function MyController($filter, $scope) {
this.testStuff = []; // your data
this.filteredResult = $filter("isArray")(this.testStuff[0][1]);
// or if you need to watch for data changes
var vm = this;
$scope.$watchCollection(
function() { return vm.testStuff },
function(newTestStuff) {
vm.filteredResult = $filter("isArray")(newTestStuff[0][1]);
}
);
}
<div ng-controller="MyController as my">
<div ng-show="my.filterResult">Hi</div>
</div>
I would separate logic from the view. Add state in scope and then check it
$scope.showHi = angular.isArray(textStuff[0][1]);
In view
<div ng-show="showHi">Hi</div>

ng-model not updating the value to object using templates

I am working on an MVC application using Angular. I need to open various bootstrap modal in one of the application pages. for that i just wrote a simple angular service to get the template for modal from a folder called templates and load at Run-time. Everything works fine except one thing. ng-model is not working for check box controls and DropDownList(select) items.
service to load template:
var defaultPath = "/app/services/dialog/templates/";
function _loadModalTemplate(templateName) {
var defer = $q.defer();
if (angular.isUndefined($templateCache.get(templateName))) {
return $http.get(defaultPath + templateName).then(function (data) {
$templateCache.put(templateName, data.data);
return defer.resolve();
});
} else {
return $.when($templateCache.get(templateName));
}
return defer.promise;
}
Controller
notebook.controller('createworkitemcontroller', ['$scope', '$modalInstance', 'workitemDataContext', 'common', 'options',
function ($scope, $modalInstance, workitemDataContext, common, options) {
$scope.activities = options.activities || [];
$scope.activity = $scope.activities[0];
}]);
Template HTML
<div class="col-lg-6">
<label class="text-xs">Activity</label>
<select class="form-control input-sm" data-ng-options="a.Name for a in activities" data-ng-model="activity"></select>
</div>
Data Binding is working fine but its not updating the property $scope.activity when any change is made. Same case with checkboxes as well but working with TextBox
I'm wondering if the assignment is breaking the chain of scope. Try adding your variables inside a dedicated object like $scope.model = {activity: ...} and see if it works. I ran into this when I started using multiple modals and controllers. Here's a fiddle I made a while back demonstrating the concept:
http://jsfiddle.net/6XDtN/
http://jsfiddle.net/6XDtN/1/
In the first one, the parent is oblivious because the child re-defined the variable, breaking the chain of scope. It isn't obvious it re-defined the variable, but there's no way to really re-assign a value without doing so.
In the second one, the complex type (i.e. an object), is not redefined, just a property is updated. Thus, the chain of scope is strong in this one.
Hope this helps!

Angular View not updating from a model change

I have 2 modals that use the same controller. One has:
<div id="addToPlaylist" ng-controller="PlaylistModalCtrl">
<select name="playlist" ng-model="playlist" ng-options="playlist.id as playlist.name|rmExt for playlist in playlists">
</select>
The other has:
<div id="newPlaylist" ng-controller="PlaylistModalCtrl">
<button ng-click="createPlaylist(playlistName);">Save</button>
In my controller, I have:
angular.module('MyApp')
.controller('PlaylistModalCtrl', function ($scope) {
$scope.playlists = [];
$scope.updatePlaylists = function() {
getPlaylist.then(function (response) {
$scope.playlists = response.data.data;
$scope.$$phase || $scope.$apply();
});
}
$scope.createPlaylist = function(playlist_name) {
addPlaylist(playlist_name).then(function(response) {
$("#newPlaylist").modal('hide');
});
}
$scope.updatePlaylists();
});
So one would expect that my first view would have an updated "playlists" in the dropdown, but this isn't the case. So how can I get that view to be updated?
You don't seem to understand how scoping works. Here is a plunkr illustrating that two different controllers have different scopes and stuff.
http://plnkr.co/edit/LqLuLvVkE9ltzcJH6XdN?p=preview
As you can clearly see, expecting two controllers to update the same variable would not work because each of them has the variable in its own isolated scope. What you can do to battle that is to implement a pubsub service that listens to some changes for some properties, use emit and broadcast angular functions, or, the best, rethink why you would need the same controller twice in your app and redesign it.

Resources