Consider this situation. I have a common logic which I want to reuse across Ember.ArrayController and Ember.ObjectController instances.
Both Ember.ArrayController and Ember.ObjectController are derived from a basic Ember.Object so I am trying something like:
AbstractController = Ember.Object.extend({
// some methods with common logic
});
AbstractArrayController = AbstractController.extend({});
AbstractObjectController = AbstractController.extend({});
The problem is I also need AbstractArrayController and AbstractObjectController to extend from their parents (Ember.ArrayController and Ember.ObjectController).
How would I achieve this sort of inheritance?
I am looking at reopen and reopenClass methods right now, maybe they could be useful: http://emberjs.com/api/classes/Ember.Object.html#method_reopen
I tried just doing something like:
Ember.Object.reopenClass({
foo: function () {
return "foo";
}.property("foo")
});
But that doesn't seem to work.
Another way to put the problem:
App.HelloController = Ember.ObjectController.extend({
foo: function () {
return "foo";
}.property("foo")
});
App.WorldController = Ember.ObjectController.extend({
foo: function () {
return "foo";
}.property("foo")
});
How to abstract the foo computed property?
reopenClass adds methods on the class object, not the instance objects. When you do:
Ember.Object.reopenClass({
foo: function () {
return "foo";
}.property("foo")
});
You are creating Ember.Object.foo().
You need to use reopen if you want to methods at an instance level, for example Ember.Object.create().foo().
To answer you question, the best way to abstract a function that many types of objects can use is with a mixin. To create a mixin you use.
var mixin = Ember.Mixin.create({
foo: function() {
return 'foo';
}
});
And to have your objects take advantage of that mixin you can use.
var MyController = Ember.ObjectController.extend(mixin, {
// ...
});
More about mixins: http://codingvalue.com/blog/emberjs-mixins/ and http://emberjs.com/api/classes/Ember.Mixin.html
Related
I currently use a {{link-to}} helper that was written by someone else to explicitly state the query params to pass to the next route and strip out others that are not stated. It looks like this:
//link-to example
{{#link-to 'route' (explicit-query-params fromDate=thisDate toDate=thisDate)} Link Text {{/link-to}}
//the helper
import {helper} from '#ember/component/helper';
import Object from '#ember/object';
import {assign} from '#ember/polyfills';
export function explicitQueryParams(params, hash) {
let values = assign({}, hash);
values._explicitQueryParams = true;
return Object.create({
isQueryParams: true,
values,
});
}
export default helper(explicitQueryParams);
// supporting method in router.js
const Router = EmberRouter.extend({
_hydrateUnsuppliedQueryParams(state, queryParams) {
if (queryParams._explicitQueryParams) {
delete queryParams._explicitQueryParams;
return queryParams;
}
return this._super(state, queryParams);
},
});
I've recently had a use case where I need to apply the same logic to a transitionTo() that is being used to redirect users from a route based on their access:
beforeModel() {
if (auth) {
this.transitionTo('route')
} else {
this.transitionTo('access-denied-route')
}
},
I am really struggling to see how I can refactor what I have in the handlebars helper to a re-usable function in the transitionTo() segment. I'm even unsure if transitionTo() forwards the same arguments as {{link-to}} or if I will have to fetch the queryParams somehow from a different location.
Any insight would be greatly appreciated.
Well first off, tapping into private methods like _hydrateUnsuppliedQueryParams is risky. It will make upgrading more difficult. Most people use resetController to clear stick query params. You could also explicitly clear the default values by passing empty values on the transition.
But, ill bite because this can be fun to figure out :) Check this ember-twiddle that does what you're wanting.
If you work from the beginning in the transitionTo case, we can see that in the router.js implementation:
transitionTo(...args) {
let queryParams;
let arg = args[0];
if (resemblesURL(arg)) {
return this._doURLTransition('transitionTo', arg);
}
let possibleQueryParams = args[args.length - 1];
if (possibleQueryParams && possibleQueryParams.hasOwnProperty('queryParams')) {
queryParams = args.pop().queryParams;
} else {
queryParams = {};
}
let targetRouteName = args.shift();
return this._doTransition(targetRouteName, args, queryParams);
}
So, if the last argument is an object with a query params obj, that's going directly into _doTransition, which ultimately calls:
_prepareQueryParams(targetRouteName, models, queryParams, _fromRouterService) {
let state = calculatePostTransitionState(this, targetRouteName, models);
this._hydrateUnsuppliedQueryParams(state, queryParams, _fromRouterService);
this._serializeQueryParams(state.handlerInfos, queryParams);
if (!_fromRouterService) {
this._pruneDefaultQueryParamValues(state.handlerInfos, queryParams);
}
}
which has the _hydrateUnsuppliedQueryParams function. So, to make this all work, you can't share the function directly from the helper you've created. Rather, just add _explicitQueryParams: true to your query params. Job done :)
The link-to version is different. The query params use
let queryParams = get(this, 'queryParams.values');
since the link-to component can take a variable number of dynamic segments and there needs to be some way to distinguish between the passed dynamic segments, a passed model, and query params.
When I tried to do unit testing for private methods in a Class getting error as private methods are only accessible inside the class. Here I added sample snippet for my class and mocha test. Kindly provide me solution to implement unit test for private methods.
Class Name: Notification.ts
class Notification {
constructor() {}
public validateTempalte() {
return true;
}
private replacePlaceholder() {
return true;
}
}
Unit Test:
import {Notification} from 'Notification';
import * as chai from "chai";
describe("Notification", function(){
describe('#validateTempalte - Validate template', function() {
it('it should return success', function() {
const result = new Notification()
chai.expect(result.validateTempalte()).to.be.equal(true);
});
});
describe('#replacePlaceholder - Replace Placeholder', function() {
it('it should return success', function() {
const result = new Notification()
// As expected getting error "Private is only accessible within class"
chai.expect(result.replacePlaceholder()).to.be.equal(true);
});
});
});
As a workaround, currently, I am changing access specifier of function replacePlaceholder to public. But I don't think its a valid approach.
A possible solution to omit Typescript checks is to access the property dynamically (Not telling wether its good).
myClass['privateProp'] or for methods: myClass['privateMethod']()
Technically, in current versions of TypeScript private methods are only compile-time checked to be private - so you can call them.
class Example {
public publicMethod() {
return 'public';
}
private privateMethod() {
return 'private';
}
}
const example = new Example();
console.log(example.publicMethod()); // 'public'
console.log(example.privateMethod()); // 'private'
I mention this only because you asked how to do it, and that is how you could do it.
Correct Answer
However, that private method must be called by some other method... otherwise it isn't called at all. If you test the behaviour of that other method, you will cover the private method in the context it is used.
If you specifically test private methods, your tests will become tightly coupled to the implementation details (i.e. a good test wouldn't need to be changed if you refactored the implementation).
Disclaimer
If you still test it at the private method level, the compiler might in the future change and make the test fail (i.e. if the compiler made the method "properly" private, or if a future version of ECMAScript added visibility keywords, etc).
In my case, I use the prototype of the object to get access to a private method. It works well and TS does not swear.
For example:
class Example {
private privateMethod() {}
}
describe() {
it('test', () => {
const example = new Example();
const exampleProto = Object.getPrototypeOf(example);
exampleProto.privateMethod();
})
}
If you use a static method then use exampleProto.constructor.privateMethod();.
In HolgerJeromin's comment, the comment issue has a succinct solution that still uses the property syntax.
The solution is to type cast your object / class to any.
Examples:
(<any>myClass).privateMethod();
const value = (<any>myClass).privateValue;
(myClass as any).privateMethod();
const value = (myClass as any).privateValue;
This method satisfies the compiler as well as the VSCode syntax highlighting.
Here are some of my notes from the issue that talks about this
Accessing via a string is more common, although I don't see why it might be more typesafe.
These features are done deliberately, therefore they are helping more than hindering.
There is probably a way to disable this type of feature so people don't copy and paste this code into production. "noImplicitAny": true, might help in the tsconfig.json
Extract out the private function into a separate/stand alone function, but don't export it externally.
This is somewhat semantically correct, since after all — a private function is private and should not be accessed by anyone except the class itself.
My subjective solution: you could define a new testing-only interface that extends the original one by adding the private methods as (implicitly public) interface methods. Then, you cast the instantiated object to this new test type. This satisfies both tsc and VS code type checking. Your example with my solution:
interface INotification {
validateTemplate(): boolean,
}
class Notification implements INotification {
constructor() {}
public validateTemplate() {
return true;
}
private replacePlaceholder() {
return true;
}
}
Testing:
import {Notification} from 'Notification';
import * as chai from "chai";
interface INotificationTest extends INotification {
replacePlaceholder(): boolean;
}
describe("Notification", function(){
describe('#validateTemplate - Validate template', function() {
it('it should return success', function() {
const result = new Notification() as INotificationTest;
chai.expect(result.validateTemplate()).to.be.equal(true);
});
});
describe('#replacePlaceholder - Replace Placeholder', function() {
it('it should return success', function() {
const result = new Notification() as INotificationTest;
// Works!
chai.expect(result.replacePlaceholder()).to.be.equal(true);
});
});
});
Advantages:
tsc and vs code do not complain
IntelliSense (or any other autocomplete) works
simple (subjectively)
If you don't want to define the original interface (INotification), you could just fully define the test one (INotificationTest) instead of extending and cast it in the same manner.
Disadvantages:
Added boilerplate
Need to have both of the interfaces updated and in sync
Potentially introducing bugs by explicitly casting as a non original type.
I leave it up to you to decide whether this is worth it or no. In my case, the positives outweigh the negatives. I have tested this with jest, but I assume that mocha.js is no different here.
Edit: but generally I would agree with Fenton's answer
// module.ts
private async privateMethod = () => "private method executed"
public async testPrivateMethods(...args) {
if (process.env.NODE_ENV === 'development') {
return this.privateMethod(...args);
}
}
Now we can reach our private method to test. In jest file:
// module.spec.js
describe('Module', () => {
let service: Module = new Module();
it('private method should be defined', () => {
expect(service.testPrivateMethods).toBeDefined();
});
}
You need to set your enviroment variable name of NODE_ENV must be development.
// .env
NODE_ENV="development"
The fun thing is that it's just a typescript error (not javascript), so you can fix it with
// #ts-expect-error
and everything works fine.
I consider it as a legitimate solution, as the goal was to suppress typescript in this particular case.
Since private methods are not accessible outside class, you can have another public method which calls replacePlaceholder() in Notification class and then test the public method.
Is this possible? Something like .observes('_data'), but not.
The use case is, I have a Decays mixin that has some CPs that say how long its been since a model has been updated. I'd like these CPs to be able to update if any of the model's attrs have changed. isDirty also won't work, since my use case uses store.push to update the model.
I don't think there's a built-in way to do it, but it wouldn't be very difficult to make one. Just create a base class for all of your models and override the set function. Something like this:
var BaseModel = DS.Model.extend({
set: function(keyName, value) {
var ret = this._super.apply(this, arguments);
var attributes = Ember.get(this.constructor, 'attributes');
if (attributes[keyName]) {
this.trigger('attributeChanged');
}
return ret;
}
});
Then, you can listen for changes to any attribute like this:
model.on('attributeChanged', function() {});
I think you can even do something like this in the case of a controller or similar object (although I haven't tested it):
Controller.extend(Ember.Evented, {
modelPropertyChanged: function() {
}.on('model.attributeChanged')
});
Below I have provided an example of what I 'think' should work, but does not. My expectation is that if a controller inherits from another controller, than it should be able to access provided behaviors in the parent class. I know that the same behavior can be achieved with 'needs', but I think it would be much cleaner if you could inherit behavior.
https://gist.github.com/4589210
You can alternatively use mixin. What it is? It is kind of multi inheritance.
App.Important = Ember.Mixin.create({
sharedBehavior: function() {
return "A winner is you!!!";
}.property()
});
App.OtherImportant = Ember.Mixin.create({
otherSharedBehavior: function() {
return "A winner is not you!!!";
}.property()
});
App.AnotherController = Ember.controller.extend(App.Important, App.OtherImportant, {
importantStuff: function() {
var ohBeehave = this.get("sharedBehavior");
if(ohBeehave) {
return ohBeehave;
} else {
return "FML";
}
}.property("sharedBehavior")
});
See http://emberjs.com/api/classes/Ember.Mixin.html
You are correct that controllers should be able to inherit properties from other controllers. Here is a working example based on your gist:
http://jsbin.com/uzeyum/1/edit
App = Ember.Application.create();
App.ApplicationController = Ember.Controller.extend({
sharedBehavior: function() {
return "A winner is you!!!";
}.property()
});
App.AnotherController = App.ApplicationController.extend({
importantStuff: function() {
var ohBeehave = this.get("sharedBehavior");
if(ohBeehave) {
return ohBeehave;
} else {
return "FML";
}
}.property("sharedBehavior")
});
That said, from experience I can tell you that inheritance is very rarely what you want. There are many reasons for this, see Prefer composition over inheritance? for detail.
By declaring an array of dependencies via the needs property rather than inheritance you will find over time that your application is less brittle and easier to test/change.
We can create Em.Object like this:
var foo = Em.Object.create({
somevar : '123'
});
and then use it:
foo.get('somevar');
but how to create a private property or method in Em.Object which would be accessable from the object but wouldnt be ourside?
There is indeed a way to have private variables in Ember objects, as follows:
MyObject = Ember.Object.extend({
init: function() {
// private variable
var a = 1;
// methods to get, set, or otherwise accesss the private variables
this.getA = function() {return a;};
this.setA = function(val) {a = val;}
// don't forget this!
this._super(...arguments);
}
});
now try
o1 = MyObject.create()
o2 = MyObject.create()
o1.setA(42);
o2.getA(); //1
In other words, you have to declare the private variables, AND any getters, setters, or other routines that want to use them, in the init hook. Of course, this means that those getters/setters will be present on each instance of the class, rather than in its prototype. That's slightly inefficient, but the same holds for any approach to private variables for classes in JavaScript.
It is conceivable that Ember could introduce a new private: {} hash on objects, but then Ember would need a lot of machinery to handle finding and controlling access to private variables across class hierarchies. It would be equivalent to redesigning or extending the language itself, which is not part of the Ember mission.
Meanwhile, the above approach works fine if the number of private instance variables is limited and the number of routines that need to access them is small. So the accepted answer that says this is impossible is, well, wrong.
It's not possible, since Ember.js does not provide any encapsulation mechanisms.
However, you can simply use some convention for private members. For example, prefix them with _ sign.
You could use a closure:
(function() {
var somePrivateProperty = 'xyz';
MyObject = Em.Object.extend({
someComputedProperty: function() {
return 'somePrivateProperty = ' + somePrivateProperty;
}).property()
})
})();
Is possible with a little trick:
var obj = Em.Em.Object.create(
new function(){
var privateVar = "this is private";
this.getPrivateVar = function(){
return privateVar ;
}
},
{
emberVar: "Ember var",
emberMethod : function(){
return this.getPrivateVar();
},
emberMethod1 : function(){
return privateVar ;
},
emberBinding : 'emberVar'
}
)
now if U try to get private var
obj.privateVar
> unknown
obj.getPrivateVar()
> "this is private"
obj.emberMethod()
> "this is private"
The only problem is that:
obj.emberMethod1()
> unknown