用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/UitbreidenOS/UitKit --skill angular命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
Guidelines and instructions for Agent execution state rollback rules
Guidelines and instructions for Agent execution step counters limits
Guidelines and instructions for Agent execution timeout limits setups
基于 SOC 职业分类
| name | angular |
| description | Build Angular applications with TypeScript, RxJS, and NgRx state management |
loadComponent routingStandalone components declare their own imports instead of relying on an NgModule. New Angular 17+ projects use standalone by default.
// Before (NgModule-based)
@Component({
selector: 'app-user-card',
templateUrl: './user-card.component.html',
})
export class UserCardComponent {}
@NgModule({
declarations: [UserCardComponent],
imports: [CommonModule, RouterModule],
exports: [UserCardComponent],
})
export class SharedModule {}
// After (standalone)
@Component({
selector: 'app-user-card',
standalone: true,
imports: [CommonModule, RouterModule],
templateUrl: './user-card.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserCardComponent {
@Input() user!: User;
}
Bootstrap a standalone application:
// main.ts
bootstrapApplication(AppComponent, {
providers: [
provideRouter(routes),
provideHttpClient(withInterceptors([authInterceptor])),
provideAnimations(),
importProvidersFrom(StoreModule.forRoot(reducers), EffectsModule.forRoot(effects)),
],
});
Angular Signals (17+) are synchronous, fine-grained reactive primitives. They integrate with the Angular change detection system without zone.js subscription management.
// BehaviorSubject pattern (RxJS)
export class CartService {
private _items = new BehaviorSubject<CartItem[]>([]);
readonly items$ = this._items.asObservable();
readonly count$ = this._items.pipe(map((items) => items.length));
add(item: CartItem) {
this._items.next([...this._items.value, item]);
}
}
// Signal pattern (Angular 17+)
export class CartService {
private _items = signal<CartItem[]>([]);
readonly items = this._items.asReadonly();
readonly count = computed(() => this._items().length);
add(item: CartItem) {
this..( [...current, item]);
}
}
signal() | BehaviorSubject | |
|---|---|---|
| Async stream | No | Yes |
| Template binding | Native, no async pipe | Requires async pipe or manual subscribe |
| Derived values | computed() | pipe(map(...)) |
| Side effects | effect() | tap() / subscribe |
| HTTP responses | Convert with toSignal() | Natural fit |
Use toSignal() to bridge Observables into signals for template use:
readonly users = toSignal(this.http.get<User[]>('/api/users'), { initialValue: [] });
// store/products.feature.ts
import { createFeature, createReducer, on, createActionGroup, emptyProps, props } from '@ngrx/store';
// Action group keeps actions co-located with the feature
export const ProductsActions = createActionGroup({
source: 'Products',
events: {
'Load Products': emptyProps(),
'Load Products Success': props<{ products: Product[] }>(),
'Load Products Failure': props<{ error: string }>(),
'Select Product': props<{ id: number }>(),
},
});
// State interface
interface ProductsState {
products: Product[];
selectedId: number | null;
loading: boolean;
error: string | null;
}
const initialState: ProductsState = {
products: [],
selectedId: null,
loading: false,
error: null,
};
// Feature creates reducer + selectors automatically
export const productsFeature = ({
: ,
: (
initialState,
(.,
({ ...state, : , : })),
(.,
({ ...state, products, : })),
(.,
({ ...state, error, : })),
(.,
({ ...state, : id })),
),
});
{ selectProducts, selectLoading, selectError } = productsFeature;
()
{
loadProducts$ = (
..(
(.),
(
..().(
( .({ products })),
( (.({ : error. }))),
)
),
)
);
() {}
}
| Operator | Use when |
|---|---|
switchMap | Latest wins — cancel previous inner observable (search typeahead, route data) |
exhaustMap | Ignore new emissions while current is running (form submit, login button) |
mergeMap | All concurrent, order not preserved (parallel HTTP requests, fire-and-forget) |
concatMap | Ordered queue — complete each before starting next (upload queue, sequential writes) |
// switchMap — cancels previous search request on new keystroke
searchControl.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
switchMap((q) => this.api.search(q)),
).subscribe((results) => this.results = results);
// exhaustMap — ignores submit clicks while request is in flight
this.submitBtn.clicks$.pipe(
exhaustMap(() => this.api.submit(this.form.value)),
).subscribe((resp) => this.onSuccess(resp));
// concatMap — process upload queue in order
this.uploadQueue$.pipe(
concatMap((file) => this.api.upload(file)),
).subscribe((result) => this.(result));
// app.routes.ts
export const routes: Routes = [
{
path: '',
redirectTo: 'dashboard',
pathMatch: 'full',
},
{
path: 'dashboard',
loadComponent: () =>
import('./features/dashboard/dashboard.component').then((m) => m.DashboardComponent),
},
{
path: 'products',
loadChildren: () =>
import('./features/products/products.routes').then((m) => m.PRODUCTS_ROUTES),
},
{
path: 'admin',
canActivate: [authGuard, adminGuard],
loadComponent: () =>
import('./features/admin/admin.component').then((m) => m.AdminComponent),
},
];
// features/products/products.routes.ts
export const PRODUCTS_ROUTES: Routes = [
{
path: '',
loadComponent: () =>
().( m.),
},
{
: ,
:
().( m.),
: { : productResolver },
},
];
OnPush instructs Angular to skip the component during change detection unless:
@Input() reference changesasync pipe emitsmarkForCheck() is called explicitly@Component({
selector: 'app-product-list',
standalone: true,
imports: [CommonModule, ProductCardComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<ng-container *ngIf="products$ | async as products; else loading">
<app-product-card
*ngFor="let p of products; trackBy: trackById"
[product]="p"
(addToCart)="onAdd(p)"
/>
</ng-container>
<ng-template #loading><app-skeleton /></ng-template>
`,
})
export class ProductListComponent {
readonly products$ = this.store.select(selectProducts);
constructor(
private store: Store,
private cdr: ChangeDetectorRef,
) {}
trackById(_: number, item: Product) { return item.id; }
onAdd(product: Product) {
this.store.dispatch(CartActions.addItem({ product }));
}
}
Always use trackBy with *ngFor in OnPush components to prevent full list re-renders on data refresh.
A standalone product search feature with Signals and lazy routing:
// features/search/search.component.ts
@Component({
selector: 'app-search',
standalone: true,
imports: [ReactiveFormsModule, AsyncPipe, ProductCardComponent],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<input [formControl]="query" placeholder="Search products..." />
@for (product of results(); track product.id) {
<app-product-card [product]="product" />
}
@if (loading()) {
<app-spinner />
}
`,
})
export class SearchComponent {
query = new FormControl('', { nonNullable: true });
loading = signal(false);
results = signal<Product[]>([]);
private destroy$ = new Subject<void>();
constructor(private api: ProductService) {
this.query.valueChanges.pipe(
debounceTime(300),
distinctUntilChanged(),
filter((q) => q.length >= 2),
( ..()),
( ..(q).(( ([])))),
(.),
).( {
..(products);
..();
});
}
() { ..(); ..(); }
}
{
: ,
:
().( m.),
}