Is there a way to selectively tell Durandal to reinitialize a view model. I am aware of the singleton vs new instance approaches to initialize view models.
//singleton since a declared object is returned
define(function() {
return { prop1: 1, prop2: 2 }
});
//new instance since a constructor is returned
define(function() {
var ctor = function(){};
return ctor;
});
I generally don't like to declare view models as singletons, but I have to do it in a special case due to sub routing which requires me to pass data from a parent router to my child router. However, the singleton has other side effects, so I was wondering: Is there a way to selectively request a new instance of the view model even if it was initially declared as a singleton?
Not that I know of - this is more of a limitation of requirejs versus Durandal, though. Once require has loaded the module, so far as I know it will always return you the same version of that module. Unless there's a way to tell require to reload the module?
The only thing I could think of would be to "reset" the view model during the activate method. If you're changing routes and finding that the activate method isn't being called, it may be because Durandal thinks that your module is already active (in which case it won't reactivate). You can change this behavior by customising the areSameItem function for the router (see this question for an explanation).
Hope that helps.
I don't know if it helps in your case, but you could use the activate() method in your view model. For more information see http://durandaljs.com/documentation/Hooking-Lifecycle-Callbacks.
Related
I am currently trying to implement the Model-View-Controller in my SAPUI5 / Fiori Project.
I managed to create an Instance of the Controller with: new sap.ui.core.mvc.Controller('controller.js')
This does not work for the Model (sap.ui.core.mvc does not contain a Model attribute).
Now I am searching a way to call functions of the Model from the Controller, to get my data.
I already tried this code: oObjModel = new sap.ui.mode.Model(), using this I cannot call functions from my Model.
I recommend you look at the walkthrough on the SAPUI5 documentation site. It shows how to initialize all the aspects of MVC in the correct way.
Models in SAPUI5 come in different classes to support different forms of data. For example, there is JSONModel, XMLModel, ODataModel, etc.
So to create a model, you need to first determine the specific type of model you need and use its specific constructor. For example, if you have JSON data (or simply a JavaScript object), you use the JSONModel:
var yourData = { "hello": "world" };
var oModel = new JSONModel(yourData);
Note that the above code assumes you are following the recommended way to use modules and that this code is wrapped with a sap.ui.define or sap.ui.require, where the module sap/ui/model/json/JSONModel is assigned to the variable JSONModel. The walkthrough shows this correct usage pattern. Accessing the constructor directly like the below is not recommended:
// Also probably works, but not the recommended way
var oModel = new sap.ui.model.json.JSONModel(yourData);
Your way of creating a controller is also not correct. You should preferably let the view instantiate the controller for you by providing it a controllerName, as shown in the walkthrough for Controllers.
<mvc:View xmlns="sap.m" xmlns:mvc="sap.ui.core.mvc" controllerName="name.of.your.controller">
<!-- ... -->
</mvc:View>
If you need to manually instantiate a controller from code, use this:
Controller.create({ name: "name.of.your.controller" }).then(function(oController) {
// Do something with oController
});
This again assumes you have the module sap/ui/core/mvc/Controller linked to the variable Controller.
Before version 1.56, you can use the now-deprecated sap.ui.controller function to create controllers instead:
sap.ui.controller("name.of.your.controller", null, /*async=*/true).then(function(oController) {
// Do something with oController
});
Be aware that both of these examples load the controller asynchonously, as synchronous XHR is being globally deprecated outside of Workers, and thus the framework recommends you to use async only. In fact, the new way of loading does not even provide an option for sync loading.
I'd like to set a variable or object in main.js which I can then reference from any backbone view.
I considered using localStorage, but the data is dynamic and a little sensitive so I wouldn't like to have it stored in localStorage as it could be manipulated by the user very easily.
Since you said "main.js" I think you're confused between RequireJS and Backbone.js. RequireJS is not part of Backbone. It is an AMD module loader which happens to be used a lot with backbone projects.
Looks like you need a RequireJS module like:
define(function (require) {
var someData;
var singleton = function () {
return {
getMyData = function(){},
setMyData = function(data){},
};
};
return singleton();
});
P.S: Above code can be made object literal, an instance of proper constructor function, es6 class of whatever. I just posted something as an example.
#TJ already gave what's needed to achieve what I call a Service within my app, borrowed from AngularJS services. I have a couple services, like i18n which is just a i18next instance.
Like you, I wanted to manage certain data relative to the app that could be shared everywhere, without putting it in the window object.
I came up with a AppState service which is just a Backbone model instance.
define(['underscore', 'backbone', 'color'], function(_, Backbone, Color) {
var State = Backbone.Model.extend({
setBrandColor: function(hexColor, options) {
return this.set({ color: new Color(hexColor) }, options);
},
getBrandColor: function() {
return this.get('color');
},
});
return new State(); // return a new instance instead of the constructor.
});
What's cool with a Backbone model is that anything within the app can listen to its events. This is inspired from React.
this.listenTo(AppState, 'change:color', this.onBrandColorChange);
Note that I prefer to use PascalCase for services even though they're instances, they're closely related to a static type in other languages.
This way, when the app state changes, other parts of the app may or may not react accordingly. Without the events, the app would need to be more coupled which is undesirable.
I read this style guide for angular from johnpapa. There is a snippet:
/*
* recommend
* Using function declarations
* and bindable members up top.
*/
function Avengers(dataservice, logger) {
var vm = this;
vm.avengers = [];
vm.getAvengers = getAvengers;
vm.title = 'Avengers';
activate();
function activate() {
return getAvengers().then(function() {
logger.info('Activated Avengers View');
});
}
function getAvengers() {
return dataservice.getAvengers().then(function(data) {
vm.avengers = data;
return vm.avengers;
});
}
}
So my question is in functions activate() and getAvengers(), they both reference variable (dataservice) and function (getAvengers()) outside of their scope. Is this proper use? Should I bind these 2 in the variable vm instead, e.g:
vm.getAvengers = getAvengers;
vm.dataservice = dataservice;
...
function activate() {
return vm.getAvengers().then(....);
}
function getAvengers() {
return vm.dataservice.getAvengers().then(.....);
}
Specifically for your case
Would say if you are meaning to use this within angular app would recommend not exposing the service, exposing it through this object does not add value and might down the road, when a less experienced developer modifies your code, might result in wonky access to shared dependencies.
If you want access to the dataservice objects functionality across multiple entities then register it as an angular service, and inject it to the different entities that need it.
In General
Both of the ways you are describing are perfectly correct use, but as is usually the case the answer which to use is "it depends."
Why you would use one for another would be if you wanted to expose the variable externally (i.e. if you wanted to let others access that object through the returned object, expecting others to dynamically change the service on your object)
So in this example you should ask yourself a few question
Do I want to expose this object through another object or do I want to let angular DI pass this along to the other controllers that need this functionality
Do I want to allow external entities to modify this object
Does exposing this service through my object make the use of the perceived use of this object more confusing?
But again for this particular case you should not expose it through your object ( through your variable vm, which is bound to the return object this, in this case )
The vm is a acronym for a view model (a object representation of your view) it is meant to be used within your view to bind elements, ui events to it. The dataservice and the logger seems to nothing to do with the view at all, they are just services used within a controller. If you assign them to the vm then you probably create a tightly coupling between your view and services thus it seems like a not a very good idea to me. You can think about the VM as a interface (glue) between your view and controller.
Here is a picture of the interactions between view model, controller, view and services.
With standard controller syntax in AngularJS, you can watch a variable like:
$scope.$watch(somethingToWatch, function() { alert('It changed!'); });
Using the controllerAs syntax, I want to react to this change in an active controller. What's the easiest way to do this?
More detail, if it helps. I have one controller in a side pane that controls the context of the application (user selection, start time, end time, etc.). So, if the user changes to a different context, the main view should react and update. I'm storing the context values in a factory and each controller is injecting that factory.
You can always use a watcher evaluator function, especially helpful to watch something on the controller instance or any object. You can actually return any variable for that matter.
var vm = this;
//Where vm is the cached controller instance.
$scope.$watch(function(){
return vm.propToWatch;
}, function() {
//Do something
}, true);//<-- turn on this if needed for deep watch
And there are also ways to use bound function to bind the this context.
$scope.$watch(angular.bind(this, function(){
return this.propToWatch;
//For a variable just return the variable here
}), listenerFn);
or even ES5 function.bind:
$scope.$watch((function(){
return this.propToWatch;
}).bind(this), listenerFn);
If you are in typescript world it gets more shorter.
$scope.$watch(()=> this.propToWatch, listenerFn);
Eventhough you can watch on the controller alias inside the controller ($scope.watch('ctrlAs.someProp'), it opens up couple of problems:
It predicts (or in other words pre-determines) the alias used for the controller in the view/route/directive/modal or anywhere the controller is used. It destroys the purpose of using controllerAs:'anyVMAlias' which is an important factor in readability too. It is easy to make typo and mistakes and maintenance headache too since using the controller you would need to know what name is defined inside the implementation.
When you unit test the controller (just the controller), you need to again test with the exact same alias defined inside the controller (Which can probably arguably an extra step if you are writing TDD), ideally should not need to when you test a controller.
Using a watcher providing watcher function against string always reduced some steps the angular $parse (which watch uses to create expression) internally takes to convert the string expression to watch function. It can be seen in the switch-case of the $parse implementation
We are using Backbone to create reusable components. We create controllers in order to setup bindings between models and views. We wish to offer people the ability to replace the models and views with their own implementations.
Since the majority of people will use the the components we provide, I don't want to force developers to configure or create anything that isn't different from the defaults.
This means that someone should be able to pass an instance of an object they want to use as a model or view to the controller, all configured and setup and ready to go, or they can pass in some configuration to what the controller will use by default.
I am not sure what the best approach is.
// Idea #1
var controller = new Controller({
dependencyA: {
conf: { // config for depedencyA }
},
dependencyB: {
conf: { // config for dependencyB }
class: custom.implement.Class
}
});
In this approach, the user doesn't have control over how to instantiate the object. What's bad about this is, for example, Backbone models take two arguments in the constructor while views only take one.
// Idea #2
var controller = new Controller({
dependencyA: {
args: ['arg1',{
opt1: 'opt-value',
}]
},
dependencyB: {
args: ['a','b','c']
class: custom.implement.Class
}
});
Args would be the arguments passed to a constructor. This means the controller calls the constructor with the args array, and again this only really benefits you if you're passing in custom configuration for default dependencies. If you want to pass your own implementation it's more awkward.
// Idea #3
var controller = new Controller({
dependencyA: new outOfBoxModel({ // configuration }),
dependencyB: new custom.imeplement.Class('a','b','c')
});
In this approach, the user is forced to instantiate the out of box model. If the model's default settings are all appropriate though, then the user is doing unnecessary work. The only bit they HAVE to do is create an instance of their own custom implementation.
I am not sure what the best approach would be here?
Of the three approaches, I most prefer approach number 3. Here is why:
It is more consistent than the other approaches. In the 3rd approach, the user only has to learn to pass in constructed instances of dependencies into the controller. In the other approaches, the user has to pass in either args, or args and a class name.
It does not violate the Single Responsibility Principle. In the first two approaches, your controller is made responsible for constructing and configuring its dependencies. This doesn't feel like dependency injection at all! I think it's better, and simpler, to leave the construction of dependencies to the user or another part of your application. In my opinion, its not a terrible thing to force the user to construct their own implementations - it gives them the freedom to define their constructors however they want, rather than forcing you to define and maintain constructor APIs for the Controllers dependencies, and forcing the user to conform to them.
A different idea:
If you have this freedom in your application, I would consider putting your Controller construction logic in a factory class or method:
var createController = function(config) {
// Parse, validate, extract relevant config items
// var a = create dependency a
// var b = create dependency b
return new Controller(a, b);
}
This approach allows you to be as fancy as you want with your definition of config - you could support all three of the config definitions you provided in your original post - although I wouldn't recommend that :-). At a minimum, I would have the factory method support a zero args invocation (in which case it would return the default construction of Controller) and one of your preferred config definitions.