How to update model in ui-codemirror - angularjs

I have two separate controllers which shared a property. If the first controller changes the property the second controller should recognize it and should change the text in the codemirror text area. I tried to figure it out in this fiddle example but I could not find a solution.
var app = angular.module('myApp', ['ui.codemirror']);
app.service('sharedProperties', function() {
var objectValue = {
data: 'test object value'
};
return {
setText: function(value) {
objectValue.data = value;
},
getText: function() {
return objectValue;
}
}
});
app.controller('myController1', function($scope, $timeout, sharedProperties) {
$scope.setText = function(text){
sharedProperties.setText(text);
console.log(sharedProperties.getText().data);
}
});
app.controller('myController2', function($scope, sharedProperties) {
$scope.editorOptions = {
lineWrapping: true,
lineNumbers: true,
readOnly: 'nocursor',
mode: 'xml'
};
$scope.mappingFile = sharedProperties.getText();
console.log($scope.mappingFile);
});
<div ng-app="myApp">
<div ng-controller="myController1">
<input type="text" ng-model="newText"></input>
<button ng-click="setText(newText)">Set Text</button><br/>
</div>
<div ng-controller="myController2">
<ui-codemirror ui-codemirror-opts="editorOptions" ng-model="mappingFile.data" ui-refresh="true"></ui-codemirror>
</div>
</div>

At first, the way that you're doing you have 2 controllers in the same page but without any relation, I'd suggest you to make one of them as child of another.
So, to achieve what you want you need do a kind of watch on that variable from the parent controller.
Steps:
Use the $broadcast to send data to the child controller
$scope.$broadcast('newText', $scope.newText);
Use $on to receive the data from the parent controller:
$scope.$on('newText', function(event, text) {
...
});
Here's the code working based on your original code:
(function() {
'use strict';
angular
.module('myApp', ['ui.codemirror'])
.controller('myController1', myController1)
.controller('myController2', myController2);
myController1.$inject = ['$scope', '$timeout'];
function myController1($scope, $timeout) {
$scope.setText = function(text) {
console.log('Sent...', $scope.newText);
$scope.$broadcast('newText', $scope.newText);
}
}
myController2.$inject = ['$scope'];
function myController2($scope) {
$scope.editorOptions = {
lineWrapping: true,
lineNumbers: true,
readOnly: 'nocursor',
mode: 'xml'
};
$scope.$on('newText', function(event, text) {
if (!text) return;
$scope.mappingFile = text;
console.log('Received... ', $scope.mappingFile);
});
}
})();
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.17.0/codemirror.min.js"></script>
<script src="https://rawgit.com/angular-ui/ui-codemirror/master/src/ui-codemirror.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css">
</head>
<body>
<div ng-app="myApp">
<div ng-controller="myController1">
<input type="text" ng-model="newText">
<button ng-click="setText()">Set Text</button>
<hr>
<div ng-controller="myController2">
<textarea ui-codemirror-opts="editorOptions" ng-model="mappingFile"></textarea>
</div>
</div>
</div>
</body>
</html>
Some notes:
You don't need to pass ngModel as parameter in your ngClick, you can access it directly in your controller simply calling $scope.newText (as I did);
<input> is a self-closing tag, so of course, you don't need to close it.
I hope it helps.

Related

Parent scope not updating from select with ng-change from directive

I've seen a couple of articles on this but still can't figure it out.
I am unable to update the parent scope from the within a directive. I have read the articles saying the scope value should not be primitive and instead it should be an object but still can't figure out why this is not working.
angular.module('moduleMihai').controller('someController',
['$scope', '$http', function ($scope, $http) {
$scope.durations = [{
field: 'yearly',
title: 'Yearly'
}, {
field: 'monthly',
title: 'Monthly'
}, {
field: 'weekly',
title: 'Weekly'
}];
$scope.selectedDuration = $scope.durations[0];
$scope.handleDurationSelection = function () {
console.log($scope.selectedDuration.field + ' selected');
$scope.someData.title[0] = "SOMETHING ELSE!!";
};
$scope.someData= {
title: ['Value1', 'Value2', 'Value3'] };
}]);
the directive doesn't have any stuff in it:
angular.module("awaCommon").directive("durationSelection", [
function () {
return {
scope: {}, // tried removing this as well as seen in some articles
restrict: "E",
templateUrl: "duration-selection.html",
link: function ($scope, $element, $attr) {
}
}
}
]);
below the duration-selection.html which contains the select:
<div ng-controller="someController">
<div>
Child: {{someData.title[0]}}
<select
ng-options="item.title for item in durations"
ng-model="selectedDuration"
ng-change="handleDurationSelection()">
</select>
</div>
So this value above in the Child: {{someData.title[0]}} - gets updated properly when value is selected. But the one in here - Parent: {{someData.title[0]}}, in the main route is not:
<div ng-controller="someController">
<div>
Parent: {{someData.title[0]}}
<duration-selection></duration-selection>
</div>
I need the parent scope to be updated in order to update different directives
The way to interact and update your parent scope from your directive is to use
event handling (emit and broadcast) Todd about events $emit and $broadcast : so here we alert the parent when there is a change from the child directive, then the parent listens for the event. I suggest minimal usage due to some bad sides
directive attribute to pass the function: we pass our function to be processed to our directive to handle or call it when needed from the directive ( for me the best method)
inside the directive to update the $scope.$parent.lngBusinessUnit, no need to pass the function to the directive again, not necessary. since the directive is the one handling the logic. we just directly update the parent straight up.
the use of $watch on the parent directive to help check for changes of the selectedDuration $watch read more: this is quite easy since we map the ngModel to the passed param of our directive using two way binding in our return->scope "=" from directive setup
Example For each of the above possibilities
Event Handling
angular.module("eventTest", [])
.controller("mainCtrl", function ($scope){
console.log("am here");
$scope.parentValue = "test";
$scope.valueToPass = ["Male", "Female"];
//let's catch the updated content
$scope.$on('childUpdated', function(event, value){
$scope.parentValue = value;
console.log("updated from child directive", value);
});
})
.directive("child", function(){
return {
restrict:'E',
scope: {
valueToPass:"="
},
templateUrl:"child.html",
controller: function ($scope){
//this is method is triggered when the select of our valueToPass is changed
$scope.childChanges = function (value){
$scope.$emit('childUpdated', value);
console.log("child emitted this:", value);
}
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="eventTest">
<div ng-controller="mainCtrl">
<h1>Event Test Just for your case, advise you read up</h1>
Parent: <b>{{parentValue}}</b>
<br>
<child value-to-pass="valueToPass"></child>
</div>
<script type='text/ng-template' id="child.html">
Child value : <b>{{menu}}<b> <br>
<select ng-model="menu" ng-change="childChanges(menu)">
<option ng-repeat="item in valueToPass">{{item}}</option>
</select>
</script>
</body>
directive attribute , using function
angular.module("eventTest", [])
.controller("mainCtrl", function ($scope){
console.log("am here");
$scope.parentValue = "test";
$scope.primaryVariable = "Male";
$scope.onChange = function (){
$scope.parentValue = $scope.primaryVariable;
}
})
.directive("child", function(){
return {
restrict:'E',
scope: {
primaryVariable:"=",
callMe:"&"//note this syntax, check angular directive doc
},
templateUrl:"child.html",
controller: function ($scope){
$scope.valueToPass = ["Male", "Female"];
//this is method is triggered when the select of our primaryVarible is changed
$scope.childChanges = function (){
$scope.callMe();
}
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="eventTest">
<div ng-controller="mainCtrl">
<h1>Directive Function Passing</h1>
Parent: <b>{{parentValue}}</b>
<br>
<child primary-variable="primaryVariable" call-me="onChange()"></child>
</div>
<script type='text/ng-template' id="child.html">
Child value : <b>{{primaryVariable}}<b> <br>
<select ng-model="primaryVariable" ng-change="childChanges()">
<option ng-repeat="item in valueToPass">{{item}}</option>
</select>
</script>
</body>
using scope.$parent
angular.module("eventTest", [])
.controller("mainCtrl", function ($scope){
console.log("am here");
$scope.parentValue = "test";
$scope.primaryVariable = "Male";
})
.directive("child", function(){
return {
restrict:'E',
scope: {
primaryVariable:"="
},
templateUrl:"child.html",
controller: function ($scope){
$scope.valueToPass = ["Male", "Female"];
//this is method is triggered when the select of our primaryVarible is changed
$scope.childChanges = function (){
$scope.$parent.parentValue = $scope.primaryVariable;
}
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="eventTest">
<div ng-controller="mainCtrl">
<h1>Using $parent</h1>
Parent: <b>{{parentValue}}</b>
<br>
<child primary-variable="primaryVariable"></child>
</div>
<script type='text/ng-template' id="child.html">
Child value : <b>{{primaryVariable}}<b> <br>
<select ng-model="primaryVariable" ng-change="childChanges()">
<option ng-repeat="item in valueToPass">{{item}}</option>
</select>
</script>
</body>
Using the $watch
angular.module("eventTest", [])
.controller("mainCtrl", function ($scope){
console.log("am here");
$scope.parentValue = "test";
$scope.primaryVariable = "Male";
$scope.$watch('primaryVariable', function(){
$scope.parentValue = $scope.primaryVariable;
});
})
.directive("child", function(){
return {
restrict:'E',
scope: {
primaryVariable:"="
},
templateUrl:"child.html",
controller: function ($scope){
$scope.valueToPass = ["Male", "Female"];
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="eventTest">
<div ng-controller="mainCtrl">
<h1>using $watch</h1>
Parent: <b>{{parentValue}}</b>
<br>
<child primary-variable="primaryVariable"></child>
</div>
<script type='text/ng-template' id="child.html">
Child value : <b>{{primaryVariable}}<b> <br>
<select ng-model="primaryVariable" ng-change="childChanges()">
<option ng-repeat="item in valueToPass">{{item}}</option>
</select>
</script>
</body>
Hope this helps

Bind search input to ng-repeat in different controller

I am building a web application using Angular. We have a Twitter-like navigation bar up top with a search box in it. Then we have a bunch of entries below, using the ng-repeat directive. I want to be able to bind the search box with the entries below. The challenge is due to the fact that our header and our entries are in two different controllers. If they were in the same controller, then we could do this:
<input type="search" ng-model="search">
<div ng-repeat="entry in entries | filter:search">
{{ entry.text }}
</div>
But since in my application the search box is in a different controller, search isn't in scope so it's not working.
Any suggestions?
If you put the search string inside a service you can share the data between both controllers.
Here is an example.
<!doctype html>
<html ng-app="app">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.js"></script>
<script>
angular.module('app', []);
angular.module('app')
.controller('Ctrl1', function($scope, ShareData) {
$scope.myData1 = ShareData;
});
angular.module('app')
.controller('Ctrl2', function($scope, ShareData) {
$scope.myData2 = ShareData;
$scope.entries = [
'1',
'2',
'3',
'11'
]
});
angular.module('app')
.service('ShareData', function(){
return {
search: "1"
}
})
</script>
</head>
<body >
<div ng-controller="Ctrl1">
<h2>Inside Ctrl 1</h2>
<input type="text" ng-model="myData1.search">
</div>
<hr>
<div ng-controller="Ctrl2">
<h2>Inside Ctrl 2</h2>
<div ng-repeat="entry in entries | filter:myData2.search">
{{entry}}
</div>
</div>
</body>
</html>
You can do one thing use $emit on rootscope and capture it in another controller:-
For example:-
<input type="search" ng-model="search">
Controller one:-
$scope.$watch('search',function(new){
$rootScope.$emit('update',new);
});
Controller Second:-
$rootScope.$on('update', function (event, data) {
$scope.search=data;
});
Secondly you can also share data from controller's via service (this one is effective)
myApp.factory('Data', function () {
var data = {
search: ''
};
return {
getSearch: function () {
return data.search;
},
setSearch: function (search) {
data.search= firstName;
}
};
});
myApp.controller('FirstCtrl', function ($scope, Data) {
$scope.firstName = '';
$scope.$watch('search', function (newValue) {
if (newValue) Data.setSearch(newValue);
});
});
myApp.controller('SecondCtrl', function ($scope, Data) {
$scope.$watch(function () { return Data.getSearch(); }, function (newValue) {
if (newValue) $scope.search = newValue;
});
});

Angular accordion doesn't update UI when data source has been changed

I start learning Angular and faced with some strange behaviour.
I want to add a new header to accordion dynamically but I accordion doesn't reflect it on UI till I explicitly click on some of his items. By some reason he doesn't react on items changes before it starts load itserlf aggain durnig DOM rendering.
var mainApp = angular.module('ui.bootstrap.demo', ['ui.bootstrap', 'ngResource']);
mainApp.factory('teamSharedObj',['$rootScope', function($rootScope) {
return {
teams: [],
peopleInTeam: [],
addNewTeam: function(item) {
console.log("add new team: " + item);
this.teams.push(item);
$rootScope.$broadcast('team.new');
},
addTeamMembers: function(team, teamMembers) {
for (var i = 0; i < teamMembers.length; i++) {
var temp;
// put in team as key-value pair
temp[team] = teamMembers[i]
console.log("add new team member: " + temp);
peopleInTeam.push(temp);
}
if (teamMembers.length != 0) {
$rootScope.$broadcast('teamMember.new');
}
}
}
}]);
mainApp.directive("addNewTeam", ['teamSharedObj', 'teamSharedObj', function (teamSharedObj) {
return {
restrict: 'A',
link: function( scope, element, attrs ) {
element.bind('click', function() {
console.log(scope.teamName)
teamSharedObj.addNewTeam(scope.teamName)
});
}
}
}])
mainApp.controller('teamListCtrl', ['$scope', 'teamSharedObj', function($scope, teamSharedObj) {
$scope.$on('team.new', function(event) {
console.log('new team ' + event);
$scope.items = teamSharedObj.teams;
}
);
$scope.oneAtATime = true;
$scope.items = ['new', 'another one'];//teamSharedObj.teams;
}]);
<!DOCTYPE html>
<html>
<head lang="en">
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular.js"></script>
<script src= "http://ajax.googleapis.com/ajax/libs/angularjs/1.2.26/angular-resource.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.11.2.js"></script>
<script src="js/test.team.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body ng-app="ui.bootstrap.demo">
<div id="teamBlock">
<input type="text" ng-model="teamName" >
<input add-new-team type="submit" value="Add new team" >
<!--<button add-book-button>Add data</button>-->
</div>
<div>
{{teamName}}
</div>
<div ng-controller="teamListCtrl">
<accordion close-others="oneAtATime" >
<accordion-group heading="{{d}}" ng-repeat="d in items">
This content is straight in the template.
</accordion-group>
</accordion>
<div ng-repeat="item in items">{{item}}</div>
</div>
</body>
</html>
Can you suggest me please a right way to notifu component about changes in its datasource?
bind is jqLite/jQuery method and does not automatically trigger the digest loop for you. This means no dirty checking will take place and the UI will not be updated to reflect the model changes.
To trigger it manually wrap the code in a call to $apply:
element.bind('click', function() {
scope.$apply(function () {
teamSharedObj.addNewTeam(scope.teamName);
});
});
And since teamSharedObj contains a reference to the array the controller can reference it directly. Then you do not need to use $broadcast:
addNewTeam: function(item) {
this.teams.push(item);
},
And:
mainApp.controller('teamListCtrl', ['$scope', 'teamSharedObj',
function($scope, teamSharedObj) {
$scope.oneAtATime = true;
$scope.items = teamSharedObj.teams;
}
]);
Demo: http://plnkr.co/edit/ZzZN7wlT10MD0rneYUBM?p=preview

How to $watch multiples properties simultaneous and interpolate into one expression?

Suppose two input fields - name and text. How to simultaneous watch this two fields and interpolate their value into one expression?
Thanks!
Update 9/7/2014:
I did this Plunkr with a working version of the code :)
Thanks Mohammad Sepahvand!
Code:
<!doctype html>
<html ng-app="myApp">
<head>
<title>Interpolate String Template Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.5/angular.js"></script>
<script type="text/javascript">
angular.module('myApp', ['emailParser']).controller('MyController', ['$scope', 'EmailParser', function ($scope, EmailParser) {
// init
$scope.to = '';
$scope.emailBody = '';
$scope.$watchCollection('[to, emailBody]', function (newValues, oldValues) {
// do stuff here
// newValues and oldValues contain the new and respectively old value
// of the observed collection array
if (newValues[0] && newValues[1]) { // there's name and some text?
$scope.previewText = EmailParser.parse(newValues[1], {to: $scope.to});
}
});
}]);
angular.module('emailParser', []).config(['$interpolateProvider', function ($interpolateProvider) {
$interpolateProvider.startSymbol('__');
$interpolateProvider.endSymbol('__');
}]).factory('EmailParser', ['$interpolate', function ($interpolate) { // create service
return {
parse: function (text, propertiesToBeInterpolated) { // handle parsing
var template = $interpolate(text);
return template(propertiesToBeInterpolated);
}
};
}]);
</script>
</head>
<body>
<h3>Instructions in readme.md file - please read before!</h3>
<div id="emailEditor" ng-controller="MyController">
<label>*Name:</label>
<input ng-model="to" type="text" placeholder="Ex.: John"/>
<br><br>
<label>*Text:</label><br>
<textarea ng-model="emailBody" cols="25" rows="10" placeholder="Write something"></textarea>
<p style="color:red;">*required</p>
<div>
<pre>__previewText__</pre>
</div>
</div>
</body>
</html>
You can use the $watchGroup method that was added in angular 1.3:
$scope.$watchGroup(['prop1', 'prop2'], function(newValues, oldValues, scope) {
var prop1 =newValues[0];
var prop2 =newValues[1];
});
Or you could use $watchCollection which has been available since angular 1.1.4:
scope.$watchCollection('[prop1, prop2]', function(newValues, oldValues){
});

ng-repeat not updating the list when adding data

my problem is that the ng-repeat is not updating automatically the data. When I press add pin in my code, the element is added correctly to the database. If I reload the page the data appear correctly, but not as angular should.
For the record, the Update and delete are working correctly.
Thanks in advance
This is my app.js code:
var app = angular.module("app", []);
app.controller("AppCtrl", function ($http) {
var app = this;
$http.get("/api/pin").success(function (data) {
app.pins = data.objects;
})
app.addPin = function (scope) {
$http.post("/api/pin", {"title":"new", "image":"http://placekitten.com/200/200/?image=" + app.pins.length})
.success(function(data) {
add.pins.push(data);
})
}
app.deletePin = function (pin) {
$http.delete("/api/pin/" + pin.id).success(function(response) {
app.pins.splice(app.pins.indexOf(pin), 1)
})
}
app.updatePin = function (pin) {
$http.put("/api/pin/" + pin.id, pin);
}
})
This is my index.html file:
<html>
<head>
<title>Pin Clone</title>
<script src="angular/angular.js"></script>
<script src="angular/angular-resource.js"></script>
<script src="js/app.js"></script>
</head>
<body ng-app="app" ng-controller="AppCtrl as app">
<button ng-click="app.addPin()">Add Pin</button>
<div ng-repeat="pin in app.pins">
<img ng-src="{{ pin.image }}" alt=""/>
<div class="ui">
<input type="text" ng-model="pin.title"/>
<button ng-click="app.updatePin(pin)">Update</button>
<button ng-click="app.deletePin(pin)">Delete</button>
</div>
</div>
</body>
</html>
First of all, you should really use $scope (Doc) in your controller. You can read more about the differences in this post.
Thus your controller would look like this.
app.controller("AppCtrl", ["$scope", "$http",
function ($scope, $http) {
$http.get("/api/pin").success(function (data) {
$scope.pins = data.objects;
});
$scope.addPin = function () {
....
};
$scope.deletePin = function (pin) {
....
};
$scope.updatePin = function (pin) {
....
};
}]);
HTML:
<body ng-app="app" ng-controller="AppCtrl">
<button ng-click="addPin()">Add Pin</button>
<div ng-repeat="pin in pins">
<img ng-src="{{ pin.image }}" alt=""/>
<div class="ui">
<input type="text" ng-model="pin.title"/>
<button ng-click="updatePin(pin)">Update</button>
<button ng-click="deletePin(pin)">Delete</button>
</div>
</div>
</body>
Finally, here comes the core part. You should call $apply (Doc) when your models change. You can read more in this blog post.
$http
.post("/api/pin", {
title: "new",
image:
"http://placekitten.com/200/200/?image="
+ $scope.pins.length
})
.success(function(data) {
$scope.$apply(function() {
$scope.pins.push(data);
});
});
Thus, the full controller code:
app.controller("AppCtrl", ["$scope", "$http",
function ($scope, $http) {
$http.get("/api/pin").success(function (data) {
$scope.pins = data.objects;
});
$scope.addPin = function () {
$http
.post("/api/pin", {
title: "new",
image:
"http://placekitten.com/200/200/?image="
+ $scope.pins.length
})
.success(function(data) {
$scope.$apply(function() {
$scope.pins.push(data);
});
});
};
$scope.deletePin = function (pin) {
$http
.delete("/api/pin/" + pin.id)
.success(function(response) {
$scope.$apply(function() {
$scope.pins.splice(
$scope.pins.indexOf(pin), 1
);
});
});
};
$scope.updatePin = function (pin) {
$http.put("/api/pin/" + pin.id, pin);
};
}]);
Cannot agree with Gavin. First, what you're doing is totally fine. Creating instance of controller is a much better practice than using $scope. Second, $apply() is not needed here.
The problem is ng-repeat created a new scope. While pin is updated, app.pins is not. You should do
var app = angular.module("app", []);
app.controller("AppCtrl", function ($http) {
var app = this;
$http.get("/api/pin").success(function (data) {
app.pins = data.objects;
})
app.addPin = function (scope) {
$http.post("/api/pin", {"title":"new", "image":"http://placekitten.com/200/200/?image=" + app.pins.length})
.success(function(data) {
add.pins.push(data);
})
}
app.deletePin = function (index) {
$http.delete("/api/pin/" + app.pins[index].id).success(function(response) {
app.pins.splice(index, 1)
})
}
app.updatePin = function (index) {
$http.put("/api/pin/" + app.pins[index].id, app.pins[index]);
}
})
and
<html>
<head>
<title>Pin Clone</title>
<script src="angular/angular.js"></script>
<script src="angular/angular-resource.js"></script>
<script src="js/app.js"></script>
</head>
<body ng-app="app" ng-controller="AppCtrl as app">
<button ng-click="app.addPin()">Add Pin</button>
<div ng-repeat="pin in app.pins track by $index">
<img ng-src="{{ pin.image }}" alt=""/>
<div class="ui">
<input type="text" ng-model="pin.title"/>
<button ng-click="app.updatePin($index)">Update</button>
<button ng-click="app.deletePin($index)">Delete</button>
</div>
</div>
</body>
</html>
check here: How to update ng-model on event click using $event in Angularjs
in posted code you've got typo error
app.addPin = function (scope) {
$http.post("/api/pin", {"title":"new", "image":"http://placekitten.com/200/200/?image=" + app.pins.length})
.success(function(data) {
// add.pins.push(data); <--- app not add
app.pins.push(data)
})
}

Resources