Sometimes we need to work with every single Control is a form. Here's how it can be done:
function flattenControls(form: AbstractControl): AbstractControl[] {
let extracted: AbstractControl[] = [ form ];
if (form instanceof FormArray || form instanceof FormGroup) {
const children = Object.values(form.controls).map(flattenControls);
extracted = extracted.concat(...children);
}
return extracted;
}
For examples use:
// returns all dirty abstract controls
extractControls(form).filter((control) => control.dirty);
// mark all controls as touched
extractControls(form).forEach((control) =>
control.markAsTouched({ onlySelf: true }));
It's really easy to add keyboard shortcuts in the template:
<textarea (keydown.ctrl.enter)="doSomething()"></textarea>
<input (keydown.enter)="...">
<input (keydown.a)="...">
<input (keydown.esc)="...">
<input (keydown.shift.esc)="...">
<input (keydown.control)="...">
<input (keydown.alt)="...">
<input (keydown.meta)="...">
<input (keydown.9)="...">
<input (keydown.tab)="...">
<input (keydown.backspace)="...">
<input (keydown.arrowup)="...">
<input (keydown.shift.arrowdown)="...">
<input (keydown.shift.control.z)="...">
<input (keydown.f4)="...">
Generally we get one service instance per the whole application.
It is also possible to create an instance of service per component or directive.
@Component({
selector: 'provide',
template: '<ng-content></ng-content>',
providers: [ Service ]
})
export class ProvideComponent {}
@Directive({
selector: '[provide]',
providers: [ Service ]
})
export class ProvideDirective {}
It's possible to use @ViewChild
(also @ViewChildren
and @ContentChild/Children
) to query for components of different types using dependency injection.
In the example below we can use @ViewChildren(Base)
to get instances of Foo
and Bar
.
abstract class Base {}
@Component({
selector: 'foo',
providers: [{ provide: Base, useExisting: Foo }]
})
class Foo extends Base {}
@Component({
selector: 'bar',
providers: [{ provide: Base, useExisting: Bar }]
})
class Bar extends Base {}
// Now we can require both types of components using Base.
@Component({ template: `<foo></foo><bar></bar>` })
class AppComponent {
@ViewChildren(Base) components: QueryList<Base>;
}
You can create own helper component and use it instead of *ngIf
.
@Component({
selector: 'loader',
template: `
<ng-content *ngIf="!loading else showLoader"></ng-content>
<ng-template #showLoader>🕚 Wait 10 seconds!</ng-template>
`
})
class LoaderComponent {
@Input() loading: boolean;
}
For usage example:
<loader [loading]="isLoading">🦊 🦄 🐉</loader>
Note that the content will be eagerly evaluated, e.g. in the snippet below
destroy-the-world
will be created before the loading even starts:
<loader [loading]="isLoading"><destroy-the-world></destroy-the-world></loader>
Here is the way to notify user that there are fields with non-valid values.
markFieldsAsTouched
function FormGroup or FormArray as an argument.
function markFieldsAsTouched(form: AbstractControl): void {
form.markAsTouched({ onlySelf: true });
if (form instanceof FormArray || form instanceof FormGroup) {
Object.values(form.controls).forEach(markFieldsAsTouched);
}
}
It's very useful to check out more general method Accessing all nested form controls by Thekiba to work with controls.
The Safe Navigation Operator helps with preventing null-reference exceptions in component template expressions. It returns object property value if it exists or null otherwise.
<p> I will work even if student is null or undefined: {{student?.name}} </p>
{{a?.b?.c}}
Underneath will be compiled to.
(_co.a == null)? null: ((_co.a.b == null)? null: _co.a.b.c));
To act upon swipes, pans, and pinhces as well as the other mobile gestures, you can use hammerjs
with HostListener
decorator, or an event binding,
npm install hammerjs
@HostListener('swiperight')
public swiperight(): void {
// Run code when a user swipes to the right
}
Here are samples on how to use all of the hammerjs
event bindings, you can use these events with a HostListener
as well:
<!-- pan events -->
<div (pan)="logEvent($event)"></div>
<div (panstart)="logEvent($event)"></div>
<div (panmove)="logEvent($event)"></div>
<div (panend)="logEvent($event)"></div>
<div (pancancel)="logEvent($event)"></div>
<div (panleft)="logEvent($event)"></div>
<div (panright)="logEvent($event)"></div>
<div (panup)="logEvent($event)"></div>
<div (pandown)="logEvent($event)"></div>
<!-- pinch events -->
<div (pinch)="logEvent($event)"></div>
<div (pinchstart)="logEvent($event)"></div>
<div (pinchmove)="logEvent($event)"></div>
<div (pinchend)="logEvent($event)"></div>
<div (pinchcancel)="logEvent($event)"></div>
<div (pinchin)="logEvent($event)"></div>
<div (pinchout)="logEvent($event)"></div>
<!-- press events -->
<div (press)="logEvent($event)"></div>
<div (pressup)="logEvent($event)"></div>
<!-- rotate events -->
<div (rotate)="logEvent($event)"></div>
<div (rotatestart)="logEvent($event)"></div>
<div (rotatemove)="logEvent($event)"></div>
<div (rotateend)="logEvent($event)"></div>
<div (rotatecancel)="logEvent($event)"></div>
<!-- swipe events -->
<div (swipe)="logEvent($event)"></div>
<div (swipeleft)="logEvent($event)"></div>
<div (swiperight)="logEvent($event)"></div>
<div (swipeup)="logEvent($event)"></div>
<div (swipedown)="logEvent($event)"></div>
<!-- tap event -->
<div (tap)="logEvent($event)"></div>
With ng-content
you can pass any elements to a component.
This simplifies creating reusable components.
@Component({
selector: 'wrapper',
template: `
<div class="wrapper">
<ng-content></ng-content>
</div>
`,
})
export class Wrapper {}
<wrapper>
<h1>Hello World!</h1>
</wrapper>
To avoid the expensive operations, we can help Angular to track which items added or removed i.e. customize the default tracking algorithm by providing a trackBy option to NgForOf.
So you can provide your custom trackBy function that will return unique identifier for each iterated item.
For example, some key value of the item. If this key value matches the previous one, then Angular won't detect changes.
trackBy takes a function that has index and item args.
@Component({
selector: 'my-app',
template: `
<ul>
<li *ngFor="let item of items; trackBy: trackByFn">{{item.id}}</li>
</ul>
`
})
export class AppComponent {
trackByFn(index, item) {
return item.id;
}
}
If trackBy is given, Angular tracks changes by the return value of the function.
Now when you change the collection, Angular can track which items have been added or removed according to the unique identifier and create/destroy only changed items.