I have a dynamic component similar to:
{{component fooProperty owner=this}}
fooProperty is data driven and sometimes comes off incorrectly, at least now in dev, but I'm afraid it may come off incorrectly in prod too (due to app versioning, persisted storage etc). Basically, I don't trust this to be always correct (ie. resolvable to a component). When the value is off, the entire app crashes though:
Uncaught Error: Assertion Failed: HTMLBars error: Could not find component named "some-inexisting-component" (no component or template with that name was found)
EmberError # ember.debug.js:19700
assert # ember.debug.js:6719
assert # ember.debug.js:19502
componentHook # ember.debug.js:10894
render # ember.debug.js:12782
render # ember.debug.js:12732
handleKeyword # ember.debug.js:46584
keyword # ember.debug.js:46709
exports.default # ember.debug.js:12483
handleKeyword # ember.debug.js:46545
handleRedirect # ember.debug.js:46531
...
I would prefer to catch such exception and prevent the entire app from crashing. I can think of a workaround eg. vetting the fooProperty return against App.__container__.lookup and returning a generic 'missing' component. But I would still prefer if there is a way to capture and handle exception as raised during rendering, if possible.
The simple answer is "No, but you can implement your own." Template engines have never had a role of exception handling. It's relevant not only for Handlebars/HTMLbars case but other template engines like JSX, Jinja, etc. as exception handling has quite a bit logic involved that has to be specified on the user side and it just not seems in the scope of presentation role they have. The main reason for that appears to be a separation of concerns which Ember Framework developers want to enforce to avoid developers ending up with a lot of additional spaghetti code in their apps which can substantially decrease overall code readability and maintainability.
A possible solution for your case:
component.js
import Component from 'ember-component';
import get from 'ember-metal/get';
import getOwner from 'ember-owner/get';
import { isEmpty } from 'ember-utils';
import computed from 'ember-computed';
export default Component.extend({
wantToRenderComponentName: 'name-that-doesnt-exist',
wantToRenderComponentNameExist: computed('wantToRenderComponentName', {
get() {
const owner = getOwner(this);
return !isEmpty(owner.lookup(`component:${get(this, 'wantToRenderComponentName')}`))
}
})
});
template.hbs
{{#if wantToRenderComponentNameExist}}
{{component wantToRenderComponentName}}
{{else}}
// handle your exception presentationally here
{{/if}}
The owner.lookup('component:component-name') would return a component instance if it is initialized within the app or undefined if it is not. I can imagine some cases where you would want to initialize some of the components at runtime if some condition is met to save memory so this is the way of checking if the component is initialized within the app and can be used in a template. It's not an exception handling as there are no exceptions raised in this code but it uses dynamic component naming, and that's why we can move the actual check for existence of the component to run-time in distinction with compile-time when you statically specify the name of the component to be rendered.
Assertions error will not be raised in production build. so your app wont crash. if component exist it will render otherwise it will silently fail.
Related
so i have a parent in controller like this
import Controller from '#ember/controller';
export default class IndexController extends Controller {
#service firebaseApp;
#service fastboot;
#tracked user =false;
async init(){
super.init(...arguments);
if (!this.fastboot.isFastBoot){
const auth = await this.firebaseApp.auth();
auth.onAuthStateChanged((user)=>{
if(user){
this.user = true
} else {
this.user = false
}
})
}
}
then call a component loadData like this
<LoadData #user={{this.user}}/>
the question is how to execute a function inside component loadData when #user change?
Triggering an action when an argument changes is not that well supported by Ember Octane primitives yet. A common approach is to use either #ember/render-modifiers or ember-render-helpers.
#ember/render-modifiers provide a {{did-update}} modifier.
ember-render-helpers provide a {{did-update}} helper. Both the modifier and the helper except a function as first position argument. That function is executed whenever one of the other positional arguments changes.
{{did-update}} modifier is helpful when the function executed needs access to a DOM element. It sets the DOM element, which it is attached to, as an argument on the function when called.
{{did-update}} helper is helpful when the function executed does not need access to a DOM element.
{{! A Glimmer template }}
{{! did-update helper }}
{{did-update this.loadData #user}}
{{! did-update modifier }}
<div class="loading" {{did-update this.showLoadingSpinner #user}} />
The main use case for {{did-update}} modifier is to ease the migration from classical #ember/component to #glimmer/component. In nearly all cases a specific modifier containing the logic, which should be executed, itself is a better solution. It provides better reusability, can be tested in isolation and has clear boundaries to the components in which it is used.
The main use case for {{did-update}} helper is to fill a gap in current Ember Octance programming model. Ember Octance provides an awesome developer experience for derived state thanks to autotracking and native getters. It provides a great experience to modify a DOM element depending on a value. But it does not have great primitives yet to trigger actions like data loading when an argument changes.
The community current experiments with different approaches to fill that gap. It seems to settle on #use decorator and resources as proposed by Chris Garret (pzuraq) in an RFC and in a recent blog post. It's available for experiments as part of ember-could-get-used-to-this package.
The {{did-update}} helper provided by ember-render-helpers is the most established solution to fill that gap until something like resources settle in Ember.
The concept of testing Vue/Js is new to me,so bear with me. Normally if there are errors related to the Vue component I can see them in the browser console.
for example if I have a Vue component:
<template>
<div>
{{ number }}
</div>
</template>
<script>
export default {}
</script>
I will see an error like this:
[Vue warn]: Property or method "number" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
Now when I want to test a specific component using test tools, how can I check for these kind of errors, I want to make sure that the component is loaded without these errors.
Today I tried using Jset to test this, and I ended up with this:
import { mount } from '#vue/test-utils'
import Component from '../Components/Example.vue'
test('it renders without errors', () => {
const wrapper = mount(Component)
expect(wrapper.isVueInstance()).toBe(true);
})
but, that doesn't do it since it will always return true even though 'number' is not defined. How can I check if the component has loaded without console errors.
This is not an error but warning, this is the reason why the component renders.
A proper testing strategy is to test the behaviour because that the framework doesn't cause warnings doesn't mean that a unit works correctly. A suite with full code coverage won't cause such warnings without a fail.
console.error calls can be additionally spied to strengthen the test but in this case this is entirely redundant:
spyOn(console, 'error');
const wrapper = mount(Component);
expect(wrapper.find('div').text()).toContain('123456'); // easier to do with a snapshot
expect(console.error).not.toHaveBeenCalled();
Notice that console and some other built-in methods should be spied and mocked with care because they interfere with testing framework's error reporting.
I'm new to Ember and it keeps confusing me about the difference between this.get() and Ember.get(). Can someone explain them briefly?
Welcome to Ember ;-)
Every object that extends Ember Observable mixin supports the get() method (among others).
When you call this.get(), the this must refer to such an object (Route, Controller, Component, your own class that extends Ember.Object and so on). Calling get() on plain object would cause a failure. Let me show the difference:
const emberObjectInstance = Ember.Object.create({
name: 'Bala'
});
emberObjectInstance.get('name'); // returns 'Bala'
const plainObject = { name: 'Bala'};
plainObject.get('name'); // causes a failure since get() is not a function
However, using Ember.get() successes in both cases:
Ember.get(emberObjectInstance, 'name'); // returns 'Bala'
Ember.get(plainObject, 'name'); // returns 'Bala', too
which can be also written with imports as follows
import { get } from '#ember/object';
get(emberObjectInstance, 'name'); // returns 'Bala'
get(plainObject, 'name'); // returns 'Bala', too
Note: not to forget, calling either of get() makes computed property get computed (in the most common cases, I don't want to dive deep now - lazy computation, volatile extensions etc), but for the sake of understanding the difference, we can work with plain values.
From own experience, I am using Ember.get() everywhere I know a plain object might be the object whose property I need to retrieve. A nice example is setupController() hook into which I may pass plain object from my unit tests to test setupController()'s functionality.
// some route:
setupController(controller, model){
this._super(...arguments);
const name = Ember.get(model, 'name'); // ***
controller.set('isNamePresentOnSetup', Ember.isPresent(name));
}
// in my unit tests I can use plain object:
...
const modelMock = { name: 'Bala' }; // plain object is enough because I use Ember.get instead of model.get() (see ***)?
const controllerMock = Ember.Object.create(); // has to be Ember.Object since I use controller.set() within setupController()
subject.setupController(controllerMock, modelMock);
assert.ok(controllerMock.get('isNamePresentOnSetup'), "property 'isNamePresentOnSetup' set up correctly if model name is present");
...
I could also user Ember.set(controller, 'isNamePresentOnSetup', Ember.isPresent(name)) and then pass plain controller mock into setupController(), too.
I think this is a good start since you are new in Ember and I am sure Ember gurus would have much more to add.
Relevant Ember docs:
https://guides.emberjs.com/v2.9.0/object-model/
https://guides.emberjs.com/v2.9.0/object-model/computed-properties/
https://guides.emberjs.com/v2.9.0/object-model/reopening-classes-and-instances/
UPDATE:
Using get() with chained paths works different than working with POJOs.
For example in objectInstance.get('a.b.c') if b is undefined the return value is undefined. Converting this to objectInstance.a.b.c when b is undefined would instead raise an exception.
There is none. foo.get('bar') is equivalent to Ember.get(foo, 'bar'). However because foo.get is defined on Ember.Object you can only call .get() on Ember Objects. Ember.get() will work on all ember objects. On Ember Objects Ember.get(foo, 'bar') is equivalent to foo.get('bar'), on every other object its equivalent to foo['bar'].
Please note that using Ember.get() or this.get() is not needed anymore for most use cases if running Ember >= 3.1, which was released in April 2018. You could now use native ES5 getters. A quick introduction to this change could be found in release notes for Ember 3.1. It's discussed more in detail in RFC 281.
There is a codemode available that helps you transition to ES5 getters: es5-getter-ember-codemod It could be run as part of ember-cli-update.
Please not that using Ember.get() or this.get() is not deprecated. It's still needed for some edge cases, that are listed in release notes linked above:
In fact there are several cases where you must still use get:
If you are calling get with a chained path. For example in this.get('a.b.c') if b is undefined the return value is undefined. Converting this to this.a.b.c when b is undefined would instead raise an exception.
If your object is using unknownProperty you must continue to use get. Using an ES5 getter on an object with unknownProperty will cause an assertion failure in development.
Ember Data returns promise proxy objects when you read an async relationship and from other API. Ember proxy objects, including promise proxies, still require that you call get to read values.
Please note that there is a special case if using ember-changeset. It provides it's own .get() implementation. Therefore Ember.get(this, 'value') and this.get('value') have different results if this is an ember-changeset. You find more information on that case in documentation of ember-changeset.
Given a route it is easy enough to get Ember to navigate to an error route if the model promise rejects. Currently I have the simple method of having an error.hbs and /routes/error.js, and if the model promise errors it loads the error page.
I have a situation where the model resolves fine, but one of its relationships does not. This relationship is used in the routes template e.g.
{{#each model.addresses as |address id|}}
{{address.line1}}
{{/each}}
So myurl.com/api/addresses/1 returns a 500 error. I see this in the console window, but Ember does not automatically transition to the error page. How can I get it to do this for all promises that reject as a result of a template requesting more data?
One solution would be to return RSVP.Promise in route model() which loads all data (also relationships instead of lazy loading them later by template) and rejects if any of asynchronous requests fail.
Ember generates the error pages by it self if im not wrong, if ember depends on an DS.error object for the transition you have to fulfill the requirements in order to get Ember to recognize an valid error, in Ember 2.x the error code MUST be 422 and has to follow jsonapi http://jsonapi.org/format/#errors-processing
I wrote up a long post on using Route error substates here -> https://pixelhandler.com/posts/emberjs-handling-failure-using-route-error-substates
So ultimately, error handling happens as the result of knowing "what broke" and "who owns the fact that it broke". Generally, if X asked for Y and Y fails, its X's job to display the error. In that example, if FooRoute triggers an ajax, and it fails, FooRoute more or less should handle the error.
But when it comes to the template causing fetches, it actually becomes the responsibility of the template.
One thing one can do is, actually bind to the various boolean properties representing the async operation on the PromiseProxy (in your case, model.addresses). The properties are as follows
isPending - ongoing work is happening
isFulfilled - it is done, and succeeded
isRejected - it is done, but failed
isSettled, - it is done (failed or succeeded)
using these flags could be as follows
{{#if model.addresses.isPending}}
loading...
{{else if mode.addresses.isRejected}}
Sorry something went wrong, {{model.addresses.reason.message}}
{{else}}
{{#each model.addresses as |address| }}
...
{{/each}}
{{/if}}
When loading data for a page, I like to ask myself, what data is critical to show? That is, if there are three sections on the page -- say backed by foo, foo.bars, and foo.baz -- are all three sections of highest priority? Or can I let one of them fail and show the other two? Let's assume that without either foo or foo.bars, we show an error page, but foo.baz is optional. In that case, I would write that as
// controller:foo
model(params) {
return this.get('store').find('foo', params.id);
},
afterModel(foo) {
return foo.get('bars'); // block on foo.bars fetch
}
Then I might have a {{foo-baz}} component that renders Loading... at first, then fetches foo.baz in the background and shows the full content later.
Curious about the proper procedure, or at least common procedure for using sproutcore-routing.
In the read me there it shows this basic example for routing:
SC.routes.add(':controller/:action/:id', MyApp, MyApp.route);
I'm assuming that in most cases MyApp.route would call the supplied action on the supplied controller. My question is more about beyond this step how you handle the setup/teardown stuff for an application where you have lots of primary views.
Are people instantiating new controllers when the controller changes as to always start with a clean slate of data and views? Or is it more common/advisable to instantiate all the controllers and such at load and simply use the routing to show/hide primary views?
I suppose the same question goes when bouncing between actions within a controller. Is it proper to do some teardown, especially on bindings/listeners, and then re-establishing them if the action is recalled?
My question may be a little fuzzy, but I'm basically wondering how people handle lots of primary views, and deal with cleanup so stuff doesn't get stale or chew up lots of resources.
I wrote a blog post that describes a method for this: http://codebrief.com/2012/02/anatomy-of-a-complex-ember-js-app-part-i-states-and-routes/
In most Ember and Sproutcore apps and examples I have seen, controllers are instantiated at app initialization. Routes drives state changes in statecharts, where controllers are updated and views are created/destroyed as needed.
I have the following setup.
in my Ember.Application.create() I have the following code:
MyApp.routes = Em.Object.create({
currentRoute: null,
gotoRoute: function(routeParams) {
console.log('MyApp.routes gotoRoute. type: ' + routeParams.type + ' action: ' + routeParams.action + " id: " + routeParams.id);
if (routeParams.type === 'expectedType' && routeParams.action === 'expectedAction' && routeParams.id) {
//find item with ID and load in controller
MyApp.MyController.findItemWithId(routeParams.id);
//Navigate to the correct state
MyApp.stateManager.goToState('stateName');
}
}
})
SC.routes.add(":action/:type/:id", MyApp.routes, 'gotoRoute');
Then, when I click on things that should cause the URL to change I do:
SC.routes.set("location", "show/item/ID-123-123");
Your app should now be listening to changes in the URL and cause the correct action to happen based on the URL-part.
You could probably move the MyApp.MyController.findItemWithId(routeParams.id); to the enter() function of the statechart (if you are using them), but you do need to store that ID somewhere in some controller.