Error handling with Angular2 async pipe - error-handling

I am using the Angular2 async pipe to stream values into the DOM. Here's a real simple example:
const stream = Observable.interval(1000)
.take(5)
.map(n => { if (n === 3) throw "ERROR"; return n; });
<div *ngFor="for num of stream | async">
{{num}}
</div>
<div id="error"></div>
What I would like to do is to have the sequence of 1-5 displayed, but on the error item (3), somehow populate the #error div with the error message.
This seems to require two things: first is the ability of the Angular async pipe to do something intelligent with errors, which I see no sign of. Looking at the source code, apparently it throws a JS exception, which doesn't seem too friendly.
Second is the ability to restart or continue the sequence after the error. I have read about catch and onErrorResumeNext and so on, but they all involve another sequence which will be switched to on an error. This greatly complicates the logic of generating the stream, on which I would just like to put a series of numbers (in this simple example). I have the sinking feeling that once an error occurs the game is over and the observable is completed and can only be "restarted" with a different observable. I'm still learning observables; is this in fact the case?
So my question is twofold:
Can Angular2's async pipe do something intelligent with errors?
Do observables have some simple way to continue after an error?

Yes you're right regarding the catch operator and the ability to do something after errors occur...
I would leverage the catch operator to catch the error and do something:
const stream = Observable.interval(1000)
.take(5)
.map(n => {
if (n === 3) {
throw Observable.throw(n);
}
return n;
})
.catch(err => {
this.error = error;
(...)
});
and in the template:
<div>{{error}}</div>
To be able to go on the initial observable, you need to create a new one starting at the point where the error occurs:
createObservable(i) {
return Observable.interval(1000)
.range(i + 1, 5 - i)
.take(5 - i)
});
}
and use it in the catch callback:
.catch(err => {
this.error = error;
return this.createObservable(err);
});
These two questions could help you:
How to resumeOnError (or similar) in RxJS5
RxJS Continue Listening After Ajax Error (last answer)

1) no, The async pipe subscribes and unsubscribes and returns the events it receives. You would need to handle the errors before they receive the async pipe.
2) You can use the catch operator and when it returns an observable then its value(s) is emitted by the .catch(err => Observable.of(-1)) instead of the error.
You could use this to emit a special "error" value and then use something like *ngIf="num === -1 to show the error value in some special way.
You can find more information on this https://blog.thoughtram.io/angular/2017/02/27/three-things-you-didnt-know-about-the-async-pipe.html

#Thierry Templier answer was correct but is now a bit outdated. Here's how to do it with the latest RXJS.
this.myObservable$ = this.myService.myFunc().pipe(
catchError(() => of([])) // this will emit [] if the request fails - u could handle this [] emit on error in the service itself
)
then HTML as normal:
<div *ngFor="let xxx of (myObservable$ | async)">
</div>
Note $ at end of Observable name is Angular recommended way to denote an Observable.

I was facing a similar issue and came up with another approach. I do not know if it's a good way of doing it, but it works.
template where you want to show the result of your observable:
<div *ngIf="tableData$ | async as tableData; else loader" class="mt-4">
<!-- do something with tableData -->
</div>
<ng-template #loader>
<loading [target]="tableData$"></loading>
</ng-template>
The loading component:
export class LoadingComponent implements OnInit {
private _errorMessageSubject : Subject<string> = new Subject<string>();
private _errorMessage$ : Observable<string> = this._errorMessageSubject.asObservable();
public get errorMessage$() : Observable<string> { return this._errorMessage$; }
private _target : Observable<any> | null = null;
public get target() : Observable<any> | null { return this._target }
// this input does nothing except catch the error and feed the
// message into the errorMessage subject.
#Input() public set target(o: Observable<any> | null) {
if(o == null) { return; }
this._target = o.pipe(
catchError((error, _) => {
this._errorMessageSubject.next(error);
return of(null);
}),
);
};
constructor() { }
ngOnInit(): void {
}
}
loader template:
<div *ngIf="target && target | async;">
</div>
<div *ngIf="errorMessage$ | async as error; else loading">
<p class="text-danger">{{ error }}</p>
</div>
<ng-template #loading> <!-- simply a spinner icon -->
<div class="d-flex justify-content-center">
<fa-icon [icon]="['fas', 'spinner']" size="6x" [spin]="true"></fa-icon>
</div>
</ng-template>
I am not perfectly sure if its a good approach to subscribe to the observable twice, as subscribing is done in the original component that needs the data and in the loader, but otherwise this seems to work properly.

Related

handle errors with HTMX

<form
class="" id="form" hx-post="/add/" hx-swap="afterbegin" hx-target="#big_list" hx-trigger="submit">
<input type="text" name="langue1" >
<input type="text" name="langue2">
<div id="errors"></div>
<button type="submit">GO</button>
</form>
<div id="big_list">
.....
</div>
I have a big list in #big_list, and I want my #form appends only one row when submitted.
How with htmx, can I handle errors and show message in #errors ?
I created this solution so you can use hx-target-error = to define which HTML will be displayed after a failed request
document.body.addEventListener('htmx:afterRequest', function (evt) {
const targetError = evt.target.attributes.getNamedItem('hx-target-error')
if (evt.detail.failed && targetError) {
document.getElementById(targetError.value).style.display = "inline";
}
});
document.body.addEventListener('htmx:beforeRequest', function (evt) {
const targetError = evt.target.attributes.getNamedItem('hx-target-error')
if (targetError) {
document.getElementById(targetError.value).style.display = "none";
}
});
If your code raises the errors (validation?), you can change target and swap behavior with response headers.
Response.Headers.Add("HX-Retarget", "#errors");
Response.Headers.Add("HX-Reswap", "innerHTML");
If you want to return a status other than 200, you have to tell htmx to accept it.
4xx would normally not do a swap in htmx. In case of validation errors you could use 422.
document.body.addEventListener('htmx:beforeOnLoad', function (evt) {
if (evt.detail.xhr.status === 422) {
evt.detail.shouldSwap = true;
evt.detail.isError = false;
}
});
It works in htmx 1.8.
If you want to remove the error message on then next sucessfull request, you could use hx-swap-oob. Out of band elements must be in the top level of the response.
So the response could look like this:
<div>
your new row data...
</div>
<div id="errors" hx-swap-oob="true"></div>
Update
You can now use the new powerful extension multi-swap to swap multiple elements arbitrarily placed and nested in the DOM tree.
See https://htmx.org/extensions/multi-swap/
Although it doesn't follow REST principles, you might consider using an swap-oob to report your error back to your user. For example, your request might return a (slightly misleading) status 200, but include content like this:
<div id="errors" hx-swap-oob="true">
There was an error processing your request...
</div>
If it's important to follow REST more precisely, then you'll want to listen to the htmx:responseError event, as mentioned by #guettli in his previous answer.

I am looking for the command in a program that filters only when 3 letters are entered

I am looking for the command in a program that filters only when 3 letters are entered. Does anyone know what command or code I need to look for to find this?
Maybe it's also a Vue-Command, because my program is written in Vue.js.
Thank you
Pass the search input to a function first then validate the input. If passes the validation, proceed with searching.
Assuming this is your search input
<input v-model="searchInput"/>
Add an input event handler
<input v-model="searchFor" #input="searchHandler"/>
Then validate the search input with searchHandler method
new Vue({
methods: {
searchHandler (text) {
if(text.length > 2){
// Write your code on here
}
}
}
})
You can make use of debouncing to perform some functionality after some time, here you can also add a condition to check for the length of the input and then execute the logic.
const input = document.getElementById("myInput");
function callApi() {
if(input.value.length >= 3) {
console.log("Hello JS")
}
}
function debounce( callback, d ) {
let timeout;
return function() {
clearTimeout(timeout);
timeout = setTimeout( callback, d );
}
}
myInput.addEventListener(
"keyup",
debounce(callApi, 500 )
);
<label for="myInput">Type something in!</label>
<input id="myInput" type="text">

How to get cypress to return children length of 0 when there isnt any children

So I am writing a test that will add a card to a container(payment-card-container) and I want to confirm an element was added later by seeing if the children have increased by 1. But I am having issues when we try to count the children length when there isnt any. I am currently using the below:
cy.get('[data-test-id="payment-card-container"]')
.children()
.its('length')
.then(length => {
const childrenLength = length;
})
But Cypress seems to get an error because it cant find the children (Error below).
Timed out retrying: Expected to find element: ``, but never found it.
Is there a way this can work when there isnt any children and it returns the value of 0?
The problem with using a jQuery expression like
Cypress.$('[data-test-id="payment-card-container"]').children().length
is you don't get the Cypress retry for async updates.
If adding a payment card calls an API, the above expression will falsely report 0 children instead of waiting for the DOM to update.
There's really no good way to handle the no-cards situation,
Except
set up your test scenario such that there are no cards initially
add a card
confirm that there is now exactly one card
If you must test for zero children, a trailing .should() will remove the error message.
cy.get('[data-test-id="payment-card-container"]')
.children()
.should('have.length', 0); // no error when should expression passes
// Add card here
cy.get('[data-test-id="payment-card-container"]')
.children()
.should('have.length', 1); // waits for async add-card operation
Tested with
<body>
<div data-test-id="payment-card-container"></div>
<script>
setTimeout(() => {
const div = document.querySelector('[data-test-id="payment-card-container"]');
const p = document.createElement('p')
div.appendChild(p)
}, 2000)
</script>
</body>
One hacky way that I could think of is this. You can use the jQuery length and children() property to check the length:
cy.get('body').then(() = > {
if (Cypress.$('[data-test-id="payment-card-container"]').children().length == 0) {
//Do Something
}
else {
//Do Something
}
})

In Cypress how to found count a selection with same ID and get the length?

I have a such HTML code.
<div id ='pages'>
<div id='wrapper'>1 </div>
<div id='wrapper'>2 </div>
</div>
I am want to find elements count with id wrapper.
I using Cypress. I'm starting to learn Cypress.
If I try:
cy.get('div#wrapper').should('have.length', 2)
I get AssertionError:
CypressError: Timed out retrying: expected 1 to equal 2
As jonrsharpe pointed out, it's invalid HTML to have multiple elements with identical id attribute.
That being said, DOM is quite smart and can recover and work even with invalid HTML. Duplicate-id elements shouldn't cause much trouble.
If you e.g. try doing document.querySelectorAll('#wrapper') it should return list of 2 elements (in your case).
Problem is, Cypress is using jQuery to query the DOM instead of using native DOM methods and I guess jQuery isn't as smart (or it's more pedantic).
That being said, I can't reproduce that error when running:
// succeeds
cy.get('div#wrapper').should('have.length', 2)
Only when querying #wrapper directly (without the preceding div):
// fails
cy.get('#wrapper').should('have.length', 2)
I reckon this is because jQuery uses a heuristic of exiting early when a selector string (#wrapper) contains only a single id (and that's why div#wrapper returns both elements).
Also, your solution in comments (cy.get('#pages') .find('div#wrapper') .should(($div) => { expect($div).to.have.length(2) })), while working, isn't ideal because it won't retry. Let me demonstrate:
In the following code, the 2nd #wrapper will appear in the DOM only after 1 sec.
describe( 'test', () => {
beforeEach(() => {
cy.document().then( doc => {
doc.body.innerHTML = `
<div id='pages'>
<div id='wrapper'>1</div>
</div>
`;
setTimeout(() => {
doc.body.innerHTML = `
<div id='pages'>
<div id='wrapper'>1</div>
<div id='wrapper'>2</div>
</div>
`;
}, 1000 );
});
});
// will fail
it('solution A', () => {
cy.get('#pages') // <- won't be retried
.find('div#wrapper') // <- only this command will be retried
.should( $div => expect($div).to.have.length(2) );
});
// will pass
it('solution B', () => {
cy.get('#pages #wrapper') // <- will be retried and succeed in 1sec
.should( $div => {
expect($div).to.have.length(2);
});
});
// will pass
it('solution C', () => {
cy.get('#pages')
.should($pages => {
// using native DOM querying
expect($pages[0].querySelectorAll('#wrapper').length).to.eq(2);
});
});
});
Thus, you should go with solution similar to B or C.

Can I make ValidationMessageFor display hint on valid data?

I have the following scenario. A form has a few inputs and under some of them there are hints like "you don't have to fill this field" etc. Now I want the regular validation messages to replace those hints if a validation error appears. When the field is valid again the hint doesn't have to show up again (I wouldn't mind if it showed up though).
Is it possible to achieve that using the standard ValidationMessageFor helper?
I guess I could patch something up using JS, since I'm already monitoring the element which contains validation message for class changes (using http://meetselva.github.io/attrchange/), so I can change the color of a whole control group on validation error.
In this case I would just need to show\hide the hint depending on whether the validation error is visible or not.
In the end the solution I used looks like this. I encapsulated each input paired with validation message inside a 'control-group' div. Then I used attrchange to monitor validation span changes.
In View:
<div class="control-group">
#Html.TextBoxFor(model => model.Something)
<p class="help-block">This is a hint.</p>
#Html.ValidationMessageFor(model => model.Something)
</div>
In css:
.control-group.error .help-block
{
display:none;
}
In JS:
function UpdateControlGroupErrorState(valmsg) {
valmsg = $(valmsg);
var controlGroup = valmsg.closest('.control-group');
if (controlGroup.find('.field-validation-error').length > 0) {
if (!controlGroup.hasClass("error")) {
controlGroup.addClass("error");
}
}
else {
if (controlGroup.hasClass("error")) {
controlGroup.removeClass("error");
}
}
}
function SetupGroupValidate(validators) {
validators.each(function () {
UpdateControlGroupErrorState(this);
$(this).attrchange({
trackValues: true,
callback: function (e) {
if (e.attributeName == "class") {
UpdateControlGroupErrorState(this);
}
}
});
});
}
$(function () {
var validators = $('.control-group span[data-valmsg-for]');
SetupGroupValidate(validators);
});