How to change current template/route by an external script? - javascript

In my ember.js-app, Ive got one template which includes a javascript-jQuery-script. Within this script I want to call ember to read out some variables, safe them, and then change the template
Like so
finishedGame = function() {
// ember create model-entry by using "this.points"
// ember change the template to "game/credits"
};
How can I use ember to hook into an independent-running script and start functionality like switching templates etc. or ist it possible to access the controller functions from elsewhere than the ember-scripts itselv?

One very bad approach would be to use a global variable to do this and then observe it from within Ember as described in this answer, then you can trigger whatever action you require, but this is definitely not recommended.
As this other answer states, if you find yourself thinking a global variable is the best solution it's a sign that something should be refactored.
So in this case I would go with calling the finishedGame() function from within an Ember scope, like the controller (probably with Ember.$ if it is jQuery) and have this function return the values you want to save, then issue a call to a transitionToRoute method (take a look at this documentation).

Related

Angular 2+ Window attribute undefined

I'm using an external library that attaches itself to the global window object (window['lib']) once the library's javascript file is loaded by the browser. I'm trying to invoke code using this library whenever a component is loaded, however everytime I try to access the object it is undefined (due to the library not having been loaded). I have tried every lifecycle hook I can think of, however nothing appears to wait for the DOM to be fully ready. For example, I want to do something like this:
ngOnInit() {
window['lib'].doStuff(); // <-- window['lib'] is undefined
}
If I wrap it in a timeout, then it becomes available. However, this looks like code smell and do not want to approach it this way:
ngOnInit() {
setTimeout(function() {
window['lib'].doStuff(); // <-- this works
});
}
What is the best / suggested / "most angular way" to approach this problem? Thanks!
Angular Lifecycle Hook: ngOnInit()
Initialize the directive/component after Angular first displays the
data-bound properties and sets the directive/component's input
properties.
Called once, after the first ngOnChanges().
This is a common problem with Angular. Older methodologies like this one that uses a global variable on the window object will piss off the way Angular loads the application and also TypeScript (during development). The reason why you have to do window['lib'] and not window.lib is because TypeScript doesn't know anything about the types of window.lib so window['lib'] is a way to force it to work.
The other part is that depending on what type of compilation you're using (AOT vs JIT), that library you're loading may or may not be ready yet (also depends on how you're loading that script/module into the application). As Commercial Suicide mentioned, you could try some of the other Angular Lifecycle Hooks but more than likely, you're going to end up settling on setTimeout. You actually don't even need to define the timeout period or you can pass 0ms (as you've done in your code). Angular just wants you to hold off on calling that function until the DOM it's finished rendering.
I personally can't wait until all the jQuery-like libraries are converted into proper ES6 modules. The days of just throwing a script tag at the bottom of the body are long gone.
setTimeout
How to get a reference to the window object in
Angular
Relevant Thread

Passing $scope to angularJs service/factory a bad idea?

I am working on a project where in there are almost 90+ modules.
All modules has a set of input fields and on submit the data should be saved on the server.
At any given point in time only one module is active. But there can be open modules in the background.
Submit button is common to all modules, meaning there is only one Submit button throughout the application.
Below picture explains it more.
The prime motto is to keep the individual module changes to minimum and a way to handle certain things(validation, reload etc) in the module from a central place.
The current approach I am planning is,
Use a 'moduleInit' directive that all module should include in its
partial.
The directive takes the $scope of the module and pass it to a
common service/factory (pushConfigService)
The pushConfigService stores and keep this scope as long as the
module is open. Once the scope is destroyed the reference of the
same will be removed from the pushConfigService.
The footer panel is another directive with Submit button in it and
calls a save function in the pushConfigService which in turn calls
a $scope function in the module to get the form data.
pushConfigService talks to a bunch of other services like
dirtyChecker, apiGenerator and finally post data to the server.
Each module will have a set of scope methods defined with some standard names. Eg: _submit, _onSubmit, _cancel, _reload etc.
Another way to handle this, broadcast the submit event and each module listens to the same. There is possibility more actions will be added to the footer panel.
So I am little bit hesitant to use the broadcast approach.
My question, Is it a good idea to pass controller scope to a service? Any alternate suggestions?
Thanks in advance.
I believe your core concept is a nice way to handle this setup. Yet I'd suggest to split business logic from UI. I don't have a sample of your code so it is a little hard to build an exact example. Yet since you're using the $scope variable I'm going to assume you're not using a styleguide like or similar to John Papa's. His ways encourage you to not use the $scope and to stay close to actual JavaScript "classes".
How does this make a difference?
Instead of passing the whole scope, you'd be able to just pass the instance of your specific module. For one it is less confusing to you and colleagues to have a concrete interface to operate on instead of having to figure out the composition of given scope. In addition it prevents services from being able to alter the $scope.
The latter could be considered a good practice. Having just the controllers alter the scope make it easy to find the code which alters and manages the UI. From there on the controller could access services to do the actual logic.
Taking it one step further
So passing the class instance instead of scope should be an easy adjustment to the already proposed setup. But please consider the following setup as well.
It seems there are quite some different ways to handle and process the data provided by the module/end user. This logic is now implemented in the controller. One might think some of these modules share similar handling methods (big assumption there). You could move this logic to, so to speak, saving strategies, in services. On activation of a module, this module will set its preferred saving strategy in the service which handles the submit button click. Or more precisely, the save data method which should be called from the onClick handler in the controller.
Now these services/strategies might be shared among controllers, potentially setting up for a better workflow and less duplicated code.

Updating scope from outside AngularJs without using "angular.element.scope"

I'd like to be able to update a scope in Angular from a function outside Angular.
E.g., if I have a jQuery plugin which returns a success callback, I'd like to be able to update the scope from that success callback. Every solution I've seen for this involves calling angular.element(selector).scope and then calling $apply on the scope that is returned. However, I've also seen many comments indicating that this doesn't work when debug info is off and thus it isn't recommended, but I haven't seen any alternative solutions.
Does anyone know of a way to update the scope from outside of Angular without using angular.element(selector).scope?
Here is the accepted solution in the post:
"You need to use $scope.$apply() if you want to make any changes to a scope value from outside the control of AngularJs like a jQuery/javascript event handler.
function change() {
alert("a");
var scope = angular.element($("#outer")).scope();
scope.$apply(function(){
scope.msg = 'Superhero';
});
}
Here is a warning that .scope() doesn't work when debug data is off in the post:
"FYI according to the docs using .scope() requires the Debug Data to be enabled but using Debug Data in production is not recommended for speed reasons. The solutions below seem to revolve around scope() – rtpHarry Dec 5 '14 at 15:12 "
I don't see any alternative solution to using .scope() in this post or in other similar posts.
AngularJS access scope from outside js function
Thanks!
Update
One possible solution to not using angular.element(selector).scope was I assigned the scope in the controller I was using FirstCtrl to the window object. I injected $window into the FirstCtrl controller and did the following:
$window.FirstCtrlScope = $scope;
Then from jQuery or other javascript I could do:
var scope=window.FirstCtrlScope;
scope.$apply(function () {
// update the scope
});
Is this a good solution or are there better solutions for updating a scope without using angular.element(selector).scope?
Thanks!
I think both ways are bad. Design which sets controllers as global variables, or access to scope by html element leads to unmaintainable application with many hidden links.
If you need cooperate with jQuery plugins (or other non-angular code), wrap it into directive with clear API (attributes, bindings and callbacks).
You can assign the scope to a data attribute on the element, then access that. Take a look here where the angular-ui-bootstrap library implemented that approach.

How can I change behavior of Meteor template events on an included template?

I have a template that shows a table of posts. The user can edit, add, and delete posts. My site has two different varieties of posts: it has a shared library of posts and each user has posts attached to their profile.
My table is the same across the site, but the CRUD operations are different based on whether it's displaying the user's posts or the site's shared post library. For example, when a user adds a post, I need to run Meteor.users.update({_id: [user ID]}, {$push: {'profile.posts': postObject}}). When the site admin adds a post, I need to run Articles.insert(postObject).
What is the best way to do this? I tried passing an argument to my template inclusion (i.e. {{> postsTable source=Articles}}) and testing for that in the event functions, but it seems this replaces the data context which means my template can no longer get to the posts. I also thought about testing the current route and using that to branch the event functions, but that method seems a bit smelly.
Alternatively, I could bind events for the templates that include the table rather than on the table template itself. This is probably the simplest solution, but I would be duplicating a lot of code across those various templates when the only thing that changes is how the resulting data is stored.
I agree that using the current route is an unfortunate mixing of concerns. I'll admit we do that in our app in a few places to get around this exact issue.
If possible, I'd recommend modifying the context to the included postTable template. You have a couple of options:
rename the context and add more variables
{{> postsTable data=this isArticles=true}}
Now your postsTable has access to both the parent context and the isArticles boolean. The only trick is that you'll need to access the context via the data namespace (or whatever you choose to call it) which may seem a little verbose.
extend the context in a helper
Add a helper to the parent template which extends its context like this:
Template.parentTemplate.helpers({
context: function() {
var result = _.clone(this);
result.isArticles = false;
return result;
}
});
Then in your template you can do:
{{> postsTable context}}
Your postsTable template will then have the parent context along with the isArticles boolean. This is the technique we prefer whenever possible.
Also see my answer to this question if you'd prefer to do this with a global helper.

Initialize Angular $scope on initial load

I would like to init the user session with some data. On loading the main outer controller (I have a "global" controller that is run for the entire app and individual controllers for each state/route) I currently check if the local session data is empty and, if so, fetch from server and return a promise.
The issue that I have is that I use directives which need this session data. Currently they run before the scope is populated. I need a solution that does not involve modifying all the directives to use promises/callbacks/etc.
On researching this I found many requests but no viable solutions. I am using UI router and looked at the grandfather state idea but that just seems to act as a master resolve that still needs to be addressed in each controller.
I have also looked at manually bootstrapping but I need to populate the scope and I don't see how to do that from angular.element.
I should add that this needs to a pause the Angular process until loading. I see suggestions for global configuration but not sure how do pause besides deferring bootstrapping.
Suggestions?
Can you do your work in main module's .run method. and set whatever you want at $rootScope.
That way it will be visible everywhere.
Angular Modules
You can use value receipe, ngtutorial.com/learn/value
You can write your provider which will perform you some functionality.
You can take a look here - Documentation for providers how to do it. From the link you can see:
You should use the Provider recipe only when you want to expose an API for application-wide configuration that must be made before the application starts.
By the way, if you want to access scope from angular.element = you can use:
angular.element(<yourElement>).scope()

Categories

Resources