Reactive non-mongo variable in angular-meteor - angularjs

I couldn't find an answer or a solution to a challenge yet: How can I bind a variable (Session variable, no mongo collection) reactively in angular-meteor?
I'm converting from standalone meteor. Here I could use the template.helper method. As I can't use templates (and iron:router) anymore with angular-meteor and angularui-router, I can't bind reactivity to the helper anymore (at least in my understanding).
I tried this in an meteor-angular controller, which belongs to a sentence.tpl file:
$scope.parsestring = function(input_string){
tokenizer(input_string);
};
$scope.sentence_type = Session.getJSON("current_sentence.sentence_type");
Tokenizing works (I can see it in the debugger), but the value is only displayed, when I reload the page. What I want to achieve is tokenizing a string from an input field into a JSON representation (the tokenizer takes care of that) and displaying it similtaniously from the JSON representation in a structured way (separate html input elements, which are created dynamically). sentence_type is the variable that should be used on the html-page to show and change the sentence type, which can change while typing.
Anybody has some hints? Maybe, I could also use some Angular feature that I don't know?
Cheers,
Jan
Code repo:
My current code looks like this:
My code looks similar to this:
angular.module('ngaignt').controller("InteractorCtrl", ['$scope', '$meteor', '$meteorCollection',
function ($scope, $meteor, $meteorCollection) {
// Autorun is necessary to make reactive variables out of the JSON returns
var c = Tracker.autorun(function (comp) {
$scope.verb_type = Session.getJSON("current_verb.type");
$scope.object_type = Session.getJSON("current_object.type");
$scope.verb_attributes = _.toArray(Session.getJSON("current_verb.attributes"));
$scope.object_attributes = _.toArray(Session.getJSON("current_object.attributes"));
if (!comp.firstRun) {
// only do not do aply at first run becaulse then apply is already running.
$scope.$apply();
}
});
$scope.parsestring = function (input_string) {
interactor(input_string);
};
//$scope.on('$destroy', function () {c.stop()});
}]);

To use reactive variables, you need a reactive computation. You may need to use Tracker.autorun:
$scope.parsestring = Tracker.autorun(function(someStringInSession){
tokenizer(Session.get(someStringInSession));
});
Or you can use Tracker.autorun(func) wherever you use a reactive variable to reactively rerun a function when the variable changes.

good question and the best answer depend on your needs.
There are 2 possible solutions:
If you want to bind a Session variable to a scope variable, use the $meteorSession service.
What it does is that every time the scope variable will change, it will change to Session variable (and trigger an autorun if it's placed inside one).
and every time the Session variable will change, the scope variable will change as well (and change the view that it's placed upon).
If you are using the Session variable just to get a variable reactive (meaning trigger an autorun), you should use getReactively . this just returns the already existing scope variable but trigger an autorun every time it changes. a good example of this can be found it our tutorial.
Note: In anyway, when you use Tracker.autorun inside Angular, you need to connect it to a scope. this can be easily done if you replace Tracker.autorun with the $meteorUtils autorun function
Would be great if you could share a repo so that I can look on the broader perspective and could better determine what's the best solution from the two.

Based on another answer about "session" reacitivity, I could solve the problem. Just use the approach described in the link https://stackoverflow.com/a/21046935/4035797. You have to substitute Deps.autorun by Tracker.autorun though as Deps is deprecated and you have to make the scope variables for use in the template reactive (e.g., $scope.sentence_type = Session.getJSON("current_sentence.sentence_type");) and not the tokenizer.

Related

Angular JS - differentiating between a save and update with a common template

I am working on a project with a PHP backend and Angular 1.x on the front end. I have a Listings model and I use a common template to create (add) and update (edit) a Listing.
Since eidt and add both use the same front end template I need to be able to detect when a user is creating and when they are editing. It seems there are several ways to do this:
I could pass a paramater in the ng-submit:
ng-submit="saveListing({{isNewListing}}"
Then I could read the value of the paramter in my controller, but I think this is overkill?
When editing a Listing some variables set for the form auto-fill
$scope.listing.id = x;
Therfore I could just check for a value in the above:
$scope.saveListing = function() {
if(listing.id) {
// update action
} else {
// save action
}
};
Is the second option a sound and non-hacky approach. I am not an Angular pro so although it seems the logical approach to me I want to ensure that I am not hot woring this.
I usually do something similar to the second approach. Since editing means you have to "get" the original record in most cases, the record should exist somewhere in the scope. I use ui-router and have a resolve for the record, which means I can check right at the top of the controller:
$scope.isEdit = record != null;
With a scope variable or similar (e.g. controllerAs vm) you can leverage the fact that you're in "edit mode" and change the UI up a bit. Instead of "+ New" on a button you can have "+ Save".
Hope that helps!
We have a large ERP system with angularJs as a front-end framework, and we are using the "check id" approach.
When updating/edit an item there would be existing id for that item.
I think the second approach is good and I don't see any drawbacks.

How to access helpers in controller and why is my find() empty?

I'm pretty new to the new meteor 1.3.1. So I've never worked with helpers before.
This is my helper:
this.helpers({
questions() {
return Questions.find({categoryId: this.categoryId});
}
});
First question: How do I access this helper (questions) within it's own controller? I tried $scope.questions, this.questions and this.play.questions (play is the alias of the controller). Everything is undefined.
In my view I iterate with ng-repeat='question in play.questions' and it works fine.
Then I thought maybe helpers can't be accessed in the controller. So I tried this:
this.questions = Questions.find({categoryId: this.categoryId});
But here the problem is that I get an empty cursor. Any idea why that is?
I assume you are in your controller function and you have used
$reactive(this).attach($scope)
before you do
this.questions = Questions.find({category: this.categoryId});
? One reason your cursor is empty when not using a helper might be that your subscription is not ready by the time you set this.questions. Therefore you should make this assignment reactive by wrapping it inside a this.autorun. By doing this the cursor gets updated as soon as your client side collection is populated.
I think instead of calling your helper from within your controller you should extract a common function and use it inside the helper and whereever else you need it. The helpers are really only used to get data to the UI if I am not mistaken (<- ?).

Call translation service from a callback registered in an app.config section

I'm relatively new to AngularJS and the problem I'm facing is one of those "I want to inject a Service into an app.config" type of scenarios, which I realise cannot be done. (I'm comfortable with the different between Service and Provider, and why a Service cannot be injected into a .config.)
What I am trying to accomplish is to use angular-schema-form together with angular-translate such that field titles in generated forms are translated.
There is an issue where someone asks how to do this, and the advice given is to take advantage of angular-schema-form's postProcess, which is a property of the Provider. This callback gives you the form object before it is rendered, giving you the opportunity to manipulate it with user code. Therefore translation could be done within here.
The postProcess method is called on the Provider, so it is done within an app.config:
app.config(function(schemaFormProvider, $translateProvider) {
schemaFormProvider.postProcess(function(form){
// within here I can inspect the form object, find all
// properties whose key is "title", and then perform
// language translation on their values.
So, that is apparently the place where I have an opportunity to manipulate control titles and so on.
Over to the angular-translate library, for me to 'manually' translate strings, I can use the $translate service. This provides both synchronous and asynchronous methods to translate a given key string. The synchronous one is $translate.instant(key).
To glue these two together, what I have tried so far (which does work) is to create a 'bridge' method like this:
var app = angular.module('myApplicationName', ['schemaForm', 'pascalprecht.translate']);
....
app.config(function(schemaFormProvider, $translateProvider) {
schemaFormProvider.postProcess(function(form){
// ... code here which iterates over properties
// and finds all control titles ...
key = app.myTranslate(key);
// ....
}
....
});
app.myTranslate = function (key) {
var service = angular.injector(['ng', 'myApplicationName']).get("$translate");
return service.instant(key);
}
This does work, but it seems ugly and unsafe (as presumably there's no guarantee $translate is ready when the callback is first invoked) and the calls to angular.injector(['ng', 'myApplicationName']).get... are presumably expensive.
Is there a better way, or is this the only way I'm going to get it done, considering the constraints of the libraries I'm working with?
I have also considered an alternative approach altogether, which would be to instead perform the translations on the schema or form objects before they are processed by angular-schema-form. This could be done from within Controllers, eliminating the problem of accessing the $translate service. I may end up going down that route, but it would still be nice to understand the best solution for the above scenario.

Refreshing translations using angular-translate

I'm using angular translate for i18n.
The particular feature I'm working on is updating the status of a book. In the service callback, if successful, I am updating my book status from, say, Open to Closed. If I view the scope (using Batarang), I can see my DOM element as such:
<span translate="Closed" class="ng-scope">Open</span>
As you can see, the translate value is being updated, but the translation itself isn't occurring on its own. I've read the docs and understand this is expected behavior. What I want to know, though, is how should I be refreshing the translated value?
Presently, I'm injecting the $translate service and executing $translate.refresh() every time I update a scope value that needs to be re-translated. I feel like that's clunky, and probably not the way I should be doing it.
Any thoughts?
You definitely should not issue a refresh for this.
just do something like this:
<span> {book.state | translate} </span>
Given that your book model has a member state to reflect it's state.
Whenever the model changes, the value of state will be re-translated.
Create a common service for translation, this will configure our translation code, in particular it will configure the location of our translation files. Create a directory src/common/translation, and a file src/common/translation/translation.js:
http://technpol.wordpress.com/2013/11/02/adding-translation-using-angular-translate-to-an-angularjs-app/
angular.module('angularTranslateApp', ['pascalprecht.translate'])
.config(function($translateProvider, $translatePartialLoaderProvider) {
$translateProvider.useLoader('$translatePartialLoader', {
urlTemplate: '/UI/assets/translation/{lang}/{part}.json'
}
});
$translateProvider.preferredLanguage('en-AU'); });

Can AngularJS ngClass expressions be nested?

I'm new to AngularJS and have been assigned a maintenance task on an app we've inherited (originally developed for us by a third-party).
At the left of a header row in a table is a small button showing either a plus (+) or minus (-) symbol to indicate whether it will expand or collapse the section when clicked. It does this using ngClass as follows.
ng-class="{false:'icon-plus',true:'icon-minus'}[day.expanded]"
I have to remove the button when there is no data in the section and thus no ability to expand. There is already a class (.plus-placeholder) for this and I was wondering if the expressions that ngClass uses can be nested to allow something like this
ng-class="{false:'plus-placeholder',true:{false:'icon-plus',true:'icon-minus'}[day.expanded]}[day.hasTrips]"
which would allow me to add a hasTrips property to day to accomplish the task.
If this is not possible I think I will need to add a property something like expandoState that returns strings 'collapsed', 'expanded' and 'empty'. so I can code the ngClass like this
ng-class="{'collapsed':'icon-plus','expanded':'icon-minus','empty':'plus-placeholder'}[day.expandoState]"
And perhaps this is a cleaner way to do it in any case. Any thoughts/suggestions? Should it be relevant, the app is using AngularJS v1.0.2.
You certainly can do either of the two options you have mentioned. The second is far preferable to the first in terms of readable code.
The expandoState property you mention should probably be a property or a method placed on the scope. Your attribute would then read something like
ng-class="{'collapsed':'icon-plus','expanded':'icon-minus','empty':'plus-placeholder'}[expandoState()]"
To put this method on the scope you would need to find the relevant controller. This will probably be wherever day is assigned to the scope. Just add
$scope.expandoState = function() {
// Return current state name, based on $scope.day
};
Alternatively, you could write a method on the controller like
$scope.buttonClass = function() {
// Return button class name, based on $scope.day
};
that just returns the class to use. This will let you write the logic from your first option in a much more readable fashion. Then you can use
ng-class="buttonClass()"

Resources