using a destroyed view for changeDetection in ngAfterViewInit - javascript

A private (but open source) Angular package in the organisation I work for has some code that looks like this:
ngAfterViewInit(): void {
setTimeout(() => {
this.changeDetector.detectChanges();
// do more things
});
}
We implement this package in our application (the package is to introduce common components so that front-end devs across the org don't implement things multiple times, and to enforce a common design language for consistency). However, when using the component this is called in, and showing and then destroying the component repeatedly, eventually it will stop working, with this error in the console:
Error: ViewDestroyedError: Attempt to use a destroyed view: detectChanges
I'm trying to find a way of making sure the code above is not being run if the component has already been destroyed. I have considered using the ngOnDestroy lifecycle method:
ngOnDestroy(): void {
this.changeDetector.detach();
}
but I'm not sure what else I would put in there to check the view is ok. I also thought about something like:
if (!this.changeDetector.destroyed) {
this.changeDetector.detectChanges();
}
but nothing like destroyed exists on ChangeDetectorRef.
What is the correct way of ensuring this error does not show and the component works, even when repeatedly showing and destroying it?

I think the best solution I have found is to use !ViewRef.destroyed and if that returns truthy then proceed, otherwise don't.
if (!(this.changeDetector as ViewRef).destroyed) {
this.changeDetector.detectChanges()
// do other tasks
}

I think destroyed does work but the syntax is like this:
if (!this.changeDetector['destroyed']) {
this.changeDetector.detectChanges();
}
Then obviously, just keep the code you have on your ngOnDestroy hook.

Related

Vue3 call a components method from another component

I'm trying to have a component call a function from another component but just from
inside the setup.
I've read this from vuemastery https://www.vuemastery.com/blog/understanding-vue-3-expose/
and I see that you can accomplish this like this:
methods: {
reset () {
this.$refs.counter.reset()
},
terminate () {
this.$refs.counter.terminate()
}
}
however, I don't have access to those methods inside the setup, and I also can't use this.$refs inside the setup. Is there a way I can do the same thing inside setup, or a way to access these methods inside the setup?
Those methods are undefined in setup, and I cannot access data setup from within those functions, and I cannot use $refs in setup.
The $refs is a very easy way to call a function from another component - but I can't seem to find a relatively easy way to do this with vue3 composition api - am I missing something?
Hey I figured it out https://vuejs.org/guide/essentials/template-refs.html#accessing-the-refs
If I go
<script>
export default {
setup(props) {
const counter = ref(null);
//now I have access to counter.value.reset after it's mounted
counter.value.reset(); //does not work! it's null here
onMounted(() => {
counter.value.reset(); //here it works
})
//make sure to return counter in return
looks like there is an easy way to do this in setup just has a caveat
you cant just call it right away because setup happens before mounted https://vuejs.org/guide/essentials/lifecycle.html
I am calling it on a trigger - so if someone selects something from a dropdown I am watching the v-model and am able to call it there no problem.
Thanks for all the input.
Putting this answer here in case anyone else needs to accomplish the same thing

How to let react electron ignore undefined error?

React electron on windows, if A is null, call A.test will make the application stop working, then the user has to close the application and restart it.
How to let react ignore the error, and continue work. The code has many A.test, I can't write everywhere if(A) A.test.
If this can't be resolved, can I print the error on the web view? So I don't have to remote visit the user's computer to see the console error.
NOTE
I think the solution is to use react error boundaries, as suggested in the console.
You already pointed out that you're using error boundaries, so after testing your scenarios in this fiddle I believe your implementation might be incorrect.
Given a similar implementation for ErrorBoundary in the docs:
class ErrorBoundary extends React.Component {
state = { hasError: '' };
render() {
return this.state.hasError ? (
<span>Oops! Something went wrong:<br />{this.state.hasError}</span>
) : this.props.children;
}
}
ErrorBoundary.getDerivedStateFromError = (error) => ({ hasError: error.toString() });
This component will render the fallback when any of its children breaks.
Error boundaries are React components that catch JavaScript errors anywhere in their child component tree, log those errors, and display a fallback UI
It will look similar to:
<MyReactApp>
<ErrorBoundary>
<ChatContent />
</ErrorBoundary>
</MyReactApp>
Now any error in ChatContent will be catch by ErrorBoundary giving you the opportunity to render the fallback like:
Oops! Something went wrong:
ReferenceError: test is not defined
The code has many A.test, I can't write every where if(A) A.test
But why? You can use some editor for multi file editing.
So you can replace A.test() to safeTest(A) function.
export const safeTest = (Obj) => {
if (Obj) {
Obj.test();
} else {
// Any action you want
}
}
It is hard to offer an answer to your question because I don't see your project codes, but if your react version is 16 you can use a special component lifecycle method that name is componentDidCatch.
Inside this method you will have these values:
componentDidCatch(error, info) {
// Do something with error and info
}
Even you can use setState inside this method and show you want. I think this method can help you for your second desire, the printing error in web view.
I tend to favor using default props. You can set a value for the component to assign to a prop if the prop is passed in undefined. For example, if your component depends on an array nested within an object, you could set that value as an empty array by default. This is especially handy when your component depends on an array of results from an API call, but the component renders before the request finishes.
If you want to make the minimal effort to catch all the unhandled errors from both main and renderer processes within Electron as well as showing them to the user via a dialog, the easy way is to use electron-unhandled which does exactly that:
After having installed it (npm i electron-unhandled), in both your main and renderer entry files (likely their root index.js), you just have to add, at the beginning:
const unhandled = require('electron-unhandled');
unhandled({ showDialog: true });
Now, that being said, it's a good practice to use a global error catcher but it's a really bad one if you use only that. You should try covering your error handling more accurately, at least method by method:
.then() { ... }.catch(err => ...) for your promises,
(..., (err, res) => { if (err !== null) { ... } ... ) for your callbacks,
try { ... } catch(err) { ... } for non-async or await-based code code parts.
And, as a side-note, I myself created a dependenciless library to make it safe and easy to create a global errors dictionary in order to well-organize your errors but there are other alternatives if this one doesn't fit your needs.
I guess the best possible solution to this would be surrounding your A.test in try and catch. In this case what you can do is catch the error is A is null and perform some error page from your side incase you want it or just keep the error silent incase you dont want to perform any operation and suppress the error and continue execution.
You can also wrap A.test in a function with try-catch and use that function instead of A.test. In this way you can avoid multiple try-catch block and you can handle the error as per your requirement here.

Prevent Vue.js from rendering component

My case is that I have a static component on the desktop and it must become carousel on mobile.
The component is rendered server side because of seo and I use is="my-component" to trigger vue on it. Typically when I duplicate the markup and check in created() the breakpoint, I can trigger some carousel constructor. However, if a breakpoint is set to desktop, vue will still rerender component which is redundant.
I know that one case may not be that effective, but I have a lot of performance and parsing problems because of vue in my previous project, so I need to keep performance in mind from the beginning.
Is it possible to somehow prevent rendering on beforeCreate() hook, but still be able to use it in some conditional?
As I have read your comment, and you would like to use something else that is not v-if, I can think only in two ways of doing it.
1) If you are using vue-router you can run make use of Lazy Loading Routes which is basically a function that can return an import('component') (which is a promise).
MobileCarousel.ts
import { isMobile } from '#/utils/mediaQuery';
const MobileCarousel = (): Promise<Vue> | void => {
if (!isMobile()) {
return;
}
return import('#/components/MobileCarousel/MobileCarousel.vue');
};
export default MobileCarousel;
Routes.ts
import MobileCarousel from '#/components/MobileCarousel/MobileCarousel.ts';
...
{
path: 'route-that-has-a-mobile-only-carousel',
name: 'mobile-only-carousel',
component: MobileCarousel,
},
enter code here
My only concern with this approach is related to the server-side rendering. As I never have played with server side-rendering with Vue I cannot assure you that it will work as you expect, you can give a try. Hope it helps you.
2) Apart from using Lazy Loading Routes, I believe that a Vue component with a render function that returns only if it is mobile also can be useful for your case.

Can I run a component method only in the client using Angular2 Universal?

I am writing an angular2 universal app. It has a d3 chart, but I was hoping to only render the d3 chart on the client side (browser) and not try to render it on the server. Is there an interface in angular2 universal that will only run a component method only on the client side?
i.e.
class ComponentWithChart implements OnInit, ngUniversalBrowser {
elem;
constructor( private viewContainerRef:ViewContainerRef) {}
ngUniversalBrowserOnInit() {
this.elem = this.viewContainerRef.element.nativeElement;
d3.select(this.elem).append('div').style({
'background-color':'yellow'
});
}
}
Is there anything like the example above that might allow me to only run the ngUniversalBrowser method only in the browser OnInit?
import { isBrowser } from 'angular2-universal';
isBrowser is a boolean that you can import in your component and then execute code coditionaly only if it's running on client.
if (isBrowser) {
// this will not run on server
}
I think i got an answer. But it's a complete hack, and i'm sure they weren't intending for you to do this.
I ran into a dead-end myself (which forced me to turn off angular-universal/prerendering). My loss might be your gain.
There are particular objects that aren't available when you are pre-rendering. Mainly, "window", ie, "document.window".
Why don't you try adding a conditional statement in your component that checks whether the object exists. If it doesn't, load up your yellow background. Otherwise, load as normal.
I'm not sure if this means your component will fail to refresh when the "true" client finishes loading. But i'm sure you can set something up to watch for "window" and make it happen.
ngInit() {
if (!window) {
// yellow background placeholder
} else
// real plumbing
}
}

Aurelia binding: property-getter called repeatedly

I'm currently learning and using Aurelia and something kind of weird (maybe normal) is happening.
When using the following code
export class NavBar {
get username() {
console.log('o_o')
return 'name' + Date.now()
}
}
And in the template ${username}, the username is always updating, several times per seconds (and console.log are logged several times as well of course).
The workaround is to simply use a function and not a getter and call ${username()} in the template. But is this behaviour normal? So should I use sometimes getter sometimes not?
Thanks!
This is normal, Aurelia polls your property for changes because it has no way of knowing when your property-getter will return a different value.
If it were a simple property (without a getter), Aurelia could observe the property directly, no polling would be needed.
To avoid the polling you could tell Aurelia's binding system what to observe:
import {computedFrom} from 'aurelia-framework';
export class Foo {
_username = 'hello';
#computedFrom('_username')
get username() {
return this._username;
}
}
Another option would be to use a one-time binding:
${username & oneTime}

Categories

Resources