Code Snippets
Here I present interesting use cases with short snippets of code that solve those problems
function tapOnce<T>(cb: (item: T) => any, index = 0) {
return function(source: Observable<T>) {
return source.pipe(
map((data, i) => ({data, i})), // get the index
tap(({data, i}) => {
if (i === index) { // check if index matches
cb(data); // perform the side effect
}
}),
map(({data}) => data), // map back to the original emission
)
}
}
of(1, 2, 3, 4, 5).pipe(
tapOnce(console.log), // will log 1
).subscribe();
function useState<State extends Record<string, any>>(state: State) {
const cdRef = inject(ChangeDetectorRef);
cdRef.detach(); // we don't need automatic change detection
setTimeout(() => cdRef.detectChanges()); // detect the very first changes when the state initializes
return new Proxy(state, {
set: (target, property: string, value) => {
(target as Record<string, any>)[property] = value; // change the state
cdRef.detectChanges(); // manually trigger the change detection
return true;
},
get(target, property: string) {
return (target as Record<string, any>)[property]; // just return the state property
},
});
}
@Component({
selector: "my-component",
template: `
<div>
{{text}}
</div>
<button (click)="onClick()">Click me!</button>
`
})
export class MyComponent {
vm = useState({text: 'Hello, World!'}); // now we have a state
onClick() {
this.vm.text = "Hello Angular"; // works as expected, changes are detected
}
get text() {
console.log('working');
return this.vm.text;
}
}
import { produce } from 'immer';
const input = [
{
type: 'notToBeChanged',
field: 'unchanged',
},
{
type: 'toBeChanged',
field: 'unchanged',
},
];
// instead of this
const result = input.map(item => {
if (item.type === 'toBeChanged') {
return ({
...item,
field: 'changed',
})
}
return item;
})
// try this
const result = produce(input, draft => {
draft.filter(item => item.type === 'toBeChanged').forEach(item => {
item.field = 'changed';
});
});
import { fromEvent, merge, interval } from 'rxjs';
import { filter, bufferWhen } from 'rxjs/operators';
const ACTIVE_EVENTS = ['click', 'scroll', 'contextmenu', 'dblclick', 'mousemove'];
// you can add as many events as you want to define "being inactive"
merge(...ACTIVE_EVENTS.map(event => fromEvent(document, event))).pipe(
bufferWhen(() => interval(10_000)),
filter(events => events.length === 0),
).subscribe(() => alert('You have been inactive for ten seconds!'));
// typesafe implementation
export function withoutKey<T extends object, K extends keyof T>(
obj: T, key: K: M,
): Omit<T, K> {
const {[]: r, ...cleared } = obj;
return cleared;
}
const obj = {
a: 1,
b: 2,
c: 3,
};
const cleared = withoutKey(obj, 'a');
cleared.b; // { b: 2, c: 3 } exists
cleared.a; // undefined, no longr present, recognized by TypeScript
// instead of this
fromEvent(document.querySelector('input'), 'input').pipe(
debounceTime(300),
map(event => (event.target as HTMLInputElement).value),
switchMap(query => getData(query)),
switchAll(),
map(({firstName, lastName}) => firstName + ' ' + lastName),
toArray(), // you will never receive this value, as "fromEvent" never completes on itself
).subscribe(console.log);
// do this
fromEvent(document.querySelector('input'), 'input').pipe(
debounceTime(300),
map(event => (event.target as HTMLInputElement).value),
switchMap(query => getData(query).pipe(
switchAll(),
map(({firstName, lastName}) => firstName + ' ' + lastName),
toArray(), // now the inner Observavble completes and emits with each "fromEvent" emission
)),
).subscribe(console.log);
const form = this.formBuilder.group({
name: [''],
age: [''],
});
form.get('age').disable();
const value = form.value;
console.log(value); // {name: ''}
const rawValue = for.getRawValue();
console.log(rawValue); // {name: '', age: ''}
responseFromServer$.pipe(
retry({
count: 3, // we can also OPTIONALLY
// provide how many times
// a user is allowed to retry
delay: () => fromEvent(
document.querySelector('#retryBtn'),
'click',
), // wait for the user to click the button
}),
);