How to call $scope.$apply() using "controller as" syntax - angularjs

I am trying to limit my use of $scope in my controllers as much as possible and replace it with the Controller as syntax.
My current problem is that i'm not sure how to call $scope.$apply() in my controller without using $scope.
Edit: I am using TypeScript 1.4 in conjunction with angular
I have this function
setWordLists() {
this.fetchInProgress = true;
var campaignId = this.campaignFactory.currentId();
var videoId = this.videoFactory.currentId();
if (!campaignId || !videoId) {
return;
}
this.wordsToTrackFactory.doGetWordsToTrackModel(campaignId, videoId)
.then((response) => {
this.fetchInProgress = false;
this.wordList = (response) ? response.data.WordList : [];
this.notUsedWordList = (response) ? response.data.NotUsedWords : [];
});
}
being called from
$scope.$on("video-switch",() => {
this.setWordLists();
});
And it's (the arrays wordList and notUsedWordList)
is not being updated in my view:
<div class="wordListWellWrapper row" ng-repeat="words in wordTrack.wordList">
<div class="col-md-5 wordListWell form-control" ng-class="(words.IsPositive)? 'posWordWell': 'negWordWell' ">
<strong class="wordListWord">{{words.Word}}</strong>
<div class="wordListIcon">
<div class="whiteFaceIcon" ng-class="(words.IsPositive)? 'happyWhiteIcon': 'sadWhiteIcon' "></div>
</div>
</div>
<div class="col-md-2">
<span aria-hidden="true" class="glyphicon-remove glyphicon" ng-click="wordTrack.removeWord(words.Word)"></span>
</div>
</div>
Along the same lines of $apply, is there another way of calling $scope.$on using Controller as?
Thanks!

To answer the question at hand here, you can use $scope() methods in a controller when using the controller-as syntax, as long as you pass $scope as a parameter to the function. However, one of the main benefits of using the controller-as syntax is not using $scope, which creates a quandary.
As was discussed in the comments, a new question will be formulated by the poster to review the specific code requiring $scope in the first place, with some recommendations for re-structuring if possible.

Related

AngularJS Communication between ng-repeat and controller

I've just started using AngularJS, and as a project I decided to build a simple app for managing bookmarks. I want to be able to maintain a list of bookmarks and add/remove items. I'm using Django with Django REST framework, and Angular.
So far I've written a service to grab the bookmarks from the database, and I can print them to the console from my controller, but ng-repeat doesn't seem to be seeing them.
Here's my code for the service:
.factory('BookmarkService', ["$http", function ($http) {
var api_url = "/api/bookmarks/";
return {
list: function () {
return $http.get(api_url).then(function (response) {
return response.data
})
}
}
}]);
And for the controller:
.controller("ListController",
["$scope", "BookmarkService", function ($scope, BookmarkService) {
$scope.bookmarks = BookmarkService.list();
console.log($scope.bookmarks);
}]);
And here's the HTML:
<div ng-controller="ListController as listCtrl">
<md-card>
<md-card-content>
<h2>Bookmarks</h2>
<md-list>
<md-list-item ng-repeat="bookmark in listCtrl.bookmarks">
<md-item-content>
<div class="md-tile-content">
<p>{[{ bookmark.name }]} - {[{ bookmark.url }]}</p> // used interpolateProvider to allow "{[{" instead of "{{"
</div>
<md-divider ng-if="!$last"></md-divider>
</md-item-content>
</md-list-item>
</md-list>
</md-card-content>
</md-card>
</div>
When I print to the console from the controller I can see a promise object but ng-repeat isn't repeating:
image of promise object
I'd really appreciate if someone could help me to find my mistake and to understand why it is happening. I'm still not entirely comfortable with how all these parts fit together.
Thanks for your time!
There's two problems that I see with the code in question.
The first is that using the controller as syntax (ng-controller="ListController as listCtrl") requires properties to be bound to the controller instance and not to the scope if you address them using the controller name. In your case,
.controller("ListController",
["BookmarkService", function (BookmarkService) {
this.bookmarks = BookmarkService.list();
console.log(this.bookmarks);
}]);
The second is that you are assigning a promise to your $scope.bookmarks property. The repeater is expecting an array of objects to iterate over. You really want to assign the value resolved by the promise to $scope.bookmarks.
Instead of this
$scope.bookmarks = BookmarkService.list();
Do this
BookmarkService.list().then(function(result){
this.bookmarks = result;
});
The final version of your controller should look something like this
.controller("ListController",
["BookmarkService", function (BookmarkService) {
this.bookmarks = BookmarkService.list();
console.log(this.bookmarks);
}]);
This is simple. Ng-repeat is not working with promises. So, you can go with two ways:
BookmarkService.list().then(function(responce){
$scope.bookmarks = responce.data;
});
Another way is to create own repiter ^)
Ng-repeat doc

what is the way to iterate the $resource.get other than doing $promise.then

Once I receive the document say $scope.var = $resource.get(/*one record*/).. I would need to read the received nested object structure (which is now in $scope.var) in order to display each and every field.
I am not able to access the key-value pairs that are present in the $scope.var. So I found a way in doing this using $scope.var.$promise.then(callback) but the format of the $scope.var is changed.
for example:
before parsing:
When I console to see what is in $scope.var, it shows as -
Resource: {$Promise: Promise, $resolved: false}
/*key-value pairs of the object*/
after parsing using $promise.then:
Here the console says
Object {_id: "...", ...}
/*key-value pairs of the object*/
Because of the above format difference I am facing the problem when trying to $update using $resource. Which says $update is not a function.
Is there any other way to access key-value pairs from the $resource.get other than using $promise.then?
Edit: Here is my original code:
Contact.service.js
'use strict';
angular.module('contacts').factory('Contact', ['$resource', function($resource) {
console.log('Iam cliked');
return $resource('/api/contacts/:id', {id: '#_id'}, {
update: {
method: 'PUT'
}
});
}]);
EditController.js
'use strict';
angular.module('contacts').controller('EditController', ['$scope', '$stateParams' ,'$location', '$rootScope', 'Contact',
function($scope, $stateParams, $location, $rootScope, Contact) {
$rootScope.PAGE = 'edit';
$scope.contact = {};
$scope.singleContact = Contact.get({ id: $stateParams.id});
console.log($scope.singleContact);
$scope.singleContact.$promise.then(function(data){
angular.forEach(data, function(value, key) {
if(key !== 'additions')
$scope.contact[key] = value;
else {
angular.forEach(data.additions, function(value, key) {
$scope.contact[key] = value;
});
}
});
console.log($scope.contact);
});
/*parsing my received nested json doument to have all straight key-value pairs and binding this $scope.contact to form-field directive 'record=contact'
I am successful doing this and able to display in web page but when I try to $update any of the field in the webpage it doesnt update. Because it is not being Resource object but it is just the object
please see the images attached[![enter image description here][1]][1]*/
$scope.delete = function(){
$scope.contact.$delete();
$location.url('/contacts');
};
}
]);
Edit.html
<section data-ng-controller='EditController'>
<h2>{{contact.firstName}} {{contact.lastName}}</h2>
<form name='editContact' class='form-horizontal'>
<form-field record='contact' recordType='contactType' field='firstName' required='true'></form-field>
<form-field record='contact' recordType='contactType' field='lastName' required='true'></form-field>
<form-field record='contact' recordType='contactType' field='{{k}}' ng-repeat=' (k, v) in contact | contactDisplayFilter: "firstName" | contactDisplayFilter: "lastName" | contactDisplayFilter: "__v" | contactDisplayFilter: "_id" | contactDisplayFilter: "userName"'></form-field>
<new-field record='contact'></new-field>
<div class='row form-group'>
<div class='col-sm-offset-2'>
<button class='btn btn-danger' ng-click='delete()'>Delete Contact</button>
</div>
</div>
</form>
form-field.html
<div class='row form-group' ng-form='{{field}}' ng-class="{'has-error': {{field}}.$dirty && {{field}}.$invalid}">
<label class='col-sm-2 control-label'>{{field | labelCase}} <span ng-if='required'>*</span></label>
<div class='col-sm-6' ng-switch='required'>
<input ng-switch-when='true' ng-model='record[field]' type='{{recordType[field]}}' class='form-control' required ng-change='update()' ng-blur='blurUpdate()' />
<div class='input-group' ng-switch-default>
<input ng-model='record[field]' type='{{recordType[field]}}' class='form-control' ng-change='update()' ng-blur='blurUpdate()' />
<span class='input-group-btn'>
<button class='btn btn-default' ng-click='remove(field)'>
<span class='glyphicon glyphicon-remove-circle'></span>
</button>
</span>
</div>
</div>
<div class='col-sm-4 has-error' ng-show='{{field}}.$dirty && {{field}}.$invalid' ng-messages='{{field}}.$error'>
<p class='control-label' ng-message='required'>{{field | labelCase}} is required.</p>
<p class='control-label' ng-repeat='(k, v) in types' ng-message='{{k}}'>{{field | labelCase}} {{v[1]}}</p>
</div>
function in form-field.js directive
$scope.blurUpdate = function(){
if($scope.live!== 'false'){
console.log($scope.record);
$scope.record.$update(function(updatedRecord){
$scope.record = updatedRecord;
});
}
};
So in the above $update gives error. saying not a function.
I want the same format in $scope.singleContact and $scope.contact. How can I achieve this?
I don't know if I understand but you want something like this:
Contact.get({id: $stateParams.id}, function (contact){
$scope.var = contact;
});
right?
It will return te promise and you will be able to use the data before loading the templates
Okay, a couple things:
Promises will not return values synchronously
You have to use a then function call. The line
$scope.singleContact = Contact.get({ id: $stateParams.id});
will not put your data into the $scope.singleContact variable immediately because that line returns a Promise and goes onto the next line without waiting for the web request to return. That's how Promises work. If you aren't familiar with them, it might not be a bad idea to read up on them. A consequence of this is that everything inside your controller (or wherever this code is) that needs a working value in $scope.singleContact needs to be inside of a then block or in some way guaranteed to be run after the promise is resolved.
So I think you want (as #EDDIE VALVERDE said):
Contact.get({ id: $stateParams.id}, function(data) {
$scope.singleContact = data;
$scope.contact = data;
});
I'm not sure what the rest of the code is doing but it looks to me like it is trying to do a deep copy of $scope.singleContact. My guess is that you're just doing this cause the promise stuff wasn't working and you can remove it...
Let me know if I missed something.
Ah, now I think I understand. Okay, for 1 I would not add $resource objects to your scope. IMO the scope is intended to be a Model in the MVC paradigm. A $resource is kindof like a model, but it's performing network calls and it's got a bunch of stuff other than data. If you put $resource in your model you remove any clear layer where you can manipulate and transform the data as it comes back from the server. And I'm sure there are other reasons. So I would not add $resource objects like Contact directly to the scope. That being said, I think you want to add a new update function, like you did with delete, only something like this:
$scope.update= function(args){
//make network call and update contact
Contact.$update({/*data from args?*/});
//get the data i just saved
Contact.$get({/* id from args */}, function(result) {
$scope.contact = result;
});
//other stuff?
};
That's my first thought, anyway...

nested sortable angular repeat not populating with $http

I'm an angular newby, and I'm trying to produce a sortable array within another sortable array using ng-sortable. When I get the data directly from a javascript file it works fine, but when I use $http to retrieve the same data, only the non-repeating data displays - I don't get any error, but the repeaters don't display at all.
Controller with test data (which works):
angular.module('demoApp').controller('TestController', function ($scope, TestService, TestDataFactory) {
$scope.testData = TestService.getTestData(TestDataFactory.Data);
});
Controller with $http:
angular.module('demoApp').controller('TestController', function ($scope, $http, TestService) {
$http.get('test/Index')
.success(function (data) {
$scope.testData = TestService.getTestData(data);
})
.error(function (data) {
alert("Error getting test data");
});
});
View:
<h1 class="dataname">{{data.name}}</h1>
<div ng-model="testData" id="test" ng-controller="TestController">
<div as-sortable="sectionSortOptions" ng-model="testData.sections">
<div class="section" ng-repeat="section in testData.sections" as-sortable-item ng-include="'test/Section'">
</div>
</div>
</div>
Test/Index returns a string of the same format as TestDataFactory.Data, and the object returned by TestService.getTestData() (which is just a data formatter) in each case is identical.
Am I missing something?
Edit:
Seems my problem is to do with the fact that my ng-include has another ng-sortable within it - here's a flattened view of the whole thing:
<h1 class="dataName">{{testData.name}}</h1>
<div as-sortable="sectionSortOptions" ng-model="testData.sections">
<div class="section" ng-repeat="section in testData.sections" as-sortable-item>
<span class="section-sortOrder" as-sortable-item-handle>Section {{section.sortOrder}}</span>
<div as-sortable="itemSortOptions" ng-model="section.items">
<div class="item" ng-repeat="item in section.items" as-sortable-item>
<div as-sortable-item-handle>
<span class="itemName">{{item.name}}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
If I comment out the the line:
<div as-sortable="sectionSortOptions" ng-model="documentPack.sections">
And the associated as-sortable-item-handle (plus the closing div tag) then I get the sortable items in the sections (but not, obviously, the section-level sortability).
Plunker here: http://plnkr.co/edit/CNLVmlGjPvkcFKRo7SjN?p=preview - uses $timeout to simulate $http ($timeout caused the same problem) but now working as it uses latest ng-sortable (see answer).
I think this is your problem:
.success(function (data) {
$scope.testData = TestService.getTestData(data);
})
It should be
.success(function(data){
$scope.testData = data;
})
The success function is called when the promise is resolved, so if you do
$scope.testData = TestService.getTestData(data);
You are probably doing another remote call just as the first one finishes, and your page will never load anything.
Also, I suggest you read the documentation for ngResource as it will probably be useful in whatever you're doing.
It's not a good practice to use $http calls in your controllers, as you're coupling them with your remote calls, server addresses, and lots of other stuff that should be in another configuration files.
OK, seems the problem was with the version of ng-sortable I had - version number was labeled 1.2.2 (same as current version), but it didn't have the latest fixes (since 15th June - so, all of 15 days ago). Not sure how unlucky I am to have hit the (probably tiny) window when this problem was there, but I'll post this as answer just in case someone else hits similar behaviour with an early-June-2015 version.

Prevent ngRepeat flicker with promise in AngularJS

I have a collection of objects, say Products, which I can interact with using $resource. On an index page, I'd like to either display the collection, or, in the case the collection is empty, display a helpful message. i.e.
In Controller
$scope.products = Products.query();
In Template
<div ng-repeat="product in products">
...
</div>
<div class="alert" ng-hide="products.length">
<p>Oops, no products!</p>
</div>
This works fine, provided the user isn't staring at the spot where the ng-repeat will occur. If they are, or if there is a delay in the response from the server, they may notice a slight flicker, before the promise is resolved.
Given that, "invoking a $resource object method immediately returns an empty reference" (see here), such a flicker will always in this example. Instead, I find myself writing:
<div class="alert" ng-hide="!products.$resolved || products.length">
<p>Oops, no products!</p>
</div>
Which takes care of the flicker. However, I'm not too keen on letting my view know exactly how the products are obtained. Especially if I change this later on. Is there anything cleaner I could do? I'm aware that a fallback for ng-repeat is in the works (see here), however, just wondering if there's a cleaner solution in the meantime.
You could use the success method to set the object:
Products.query(function(data) {
$scope.products = data;
});
Or use the promise:
Products.query().$promise.then(function(data) {
$scope.products = data;
});
This way, the object doesn't become empty until you get a response.
You can get $promise out of $resource and change displayed information before/after promise is resolved.
Say you have following Products and service to get them.
/* products */
[
{ "id":1, "name":"name1" },
{ "id":2, "name":"name2" },
...
]
/***/
app.factory('Products', function ($resource) {
return $resource('products.json');
});
Then in your controller assign data only after promise is resolved.
app.controller('MainCtrl', function ($scope, Products) {
$scope.initialize = function () {
$scope.products = null;
Products.query().$promise.then(function (data) {
$scope.products = data;
});
};
$scope.initialize();
});
In your HTML template you can take care of the cases like a) not yet resolved b) resolved c) resolved but no data
<body ng-controller="MainCtrl">
<div ng-show="!products">
Getting data... please wait
</div>
<div ng-show="products && products.length === 0">
Oh noes!1 :( No products
</div>
<div ng-show="products">
<span ng-repeat="product in products">
{{ product | json }} <br>
</span>
</div>
<div>
<button type="button" ng-click="initialize()">Refresh</button>
</div>
</body>
Related plunker here http://plnkr.co/edit/Ggzyz9

AngularJS - Using a Service as a Model, ng-repeat not updating

I'm creating an ajax search page which will consist of a search input box, series of filter drop-downs and then a UL where the results are displayed.
As the filters part of the search will be in a separate place on the page, I thought it would be a good idea to create a Service which deals with coordinating the inputs and the ajax requests to a search server-side. This can then be called by a couple of separate Controllers (one for searchbox and results, and one for filters).
The main thing I'm struggling with is getting results to refresh when the ajax is called. If I put the ajax directly in the SearchCtrl Controller, it works fine, but when I move the ajax out to a Service it stops updating the results when the find method on the Service is called.
I'm sure it's something simple I've missed, but I can't seem to see it.
Markup:
<div ng-app="jobs">
<div data-ng-controller="SearchCtrl">
<div class="search">
<h2>Search</h2>
<div class="box"><input type="text" id="search" maxlength="75" data-ng-model="search_term" data-ng-change="doSearch()" placeholder="Type to start your search..." /></div>
</div>
<div class="search-summary">
<p><span class="field">You searched for:</span> {{ search_term }}</p>
</div>
<div class="results">
<ul>
<li data-ng-repeat="item in searchService.results">
<h3>{{ item.title }}</h3>
</li>
</ul>
</div>
</div>
</div>
AngularJS:
var app = angular.module('jobs', []);
app.factory('searchService', function($http) {
var results = [];
function find(term) {
$http.get('/json/search').success(function(data) {
results = data.results;
});
}
//public API
return {
results: results,
find: find
};
});
app.controller("SearchCtrl", ['$scope', '$http', 'searchService', function($scope, $http, searchService) {
$scope.search_term = '';
$scope.searchService = searchService;
$scope.doSearch = function(){
$scope.searchService.find($scope.search_term);
};
$scope.searchService.find();
}]);
Here is a rough JSFiddle, I've commented out the ajax and I'm just updating the results variable manually as an example. For brevity I've not included the filter drop-downs.
http://jsfiddle.net/XTQSu/1/
I'm very new to AngularJS, so if I'm going about it in totally the wrong way, please tell me so :)
In your HTML, you need to reference a property defined on your controller's $scope. One way to do that is to bind $scope.searchService.results to searchService.results once in your controller:
$scope.searchService.results = searchService.results;
Now this line will work:
<li data-ng-repeat="item in searchService.results">
In your service, use angular.copy() rather than assigning a new array reference to results, otherwise your controller's $scope will lose its data-binding:
var new_results = [{ 'title': 'title 3' },
{ 'title': 'title 4' }];
angular.copy(new_results, results);
Fiddle. In the fiddle, I commented out the initial call to find(), so you can see an update happen when you type something into the search box.
The problem is that you're never updating your results within your scope. There are many approaches to do this, but based on your current code, you could first modify your find function to return the results:
function find(term) {
$http.get('/json/search').success(function(data) {
var results = data.results;
});
//also notice that you're not using the variable 'term'
//to perform a query in your webservice
return results;
}
You're using a module pattern in your 'public API' so your searchService returns the find function and an array of results, but you'd want to make the function find to be the responsible for actually returning the results.
Setting that aside, whenever you call doSearch() in your scope, you'd want to update the current results for those returned by your searchService
$scope.doSearch = function(){
$scope.searchService.results = searchService.find($scope.search_term);
};
I updated your jsfiddle with my ideas, is not functional but i added some commments and logs to help you debug this issue. http://jsfiddle.net/XTQSu/3/

Resources