@casl/angular
Advanced tools
Comparing version 3.0.6 to 4.0.0
@@ -5,2 +5,28 @@ # Change Log | ||
# [4.0.0](https://github.com/stalniy/casl/compare/@casl/angular@3.0.6...@casl/angular@4.0.0) (2020-04-09) | ||
### Bug Fixes | ||
* **angular:** makes sure reflect polyfill is added before angular imports ([2acbb35](https://github.com/stalniy/casl/commit/2acbb35cc179275e23f3d83dd27ef8d5a3ef1099)), closes [#248](https://github.com/stalniy/casl/issues/248) | ||
### chore | ||
* **angular:** special commit for breaking changes ([077bcab](https://github.com/stalniy/casl/commit/077bcab8e080102b727ca697a06a8731120634fc)) | ||
### Features | ||
* **ability:** improves typing for GetSubjectName and adds default values for generics ([c089293](https://github.com/stalniy/casl/commit/c08929301a1b06880c054cbb2f21cda3725028a4)), closes [#256](https://github.com/stalniy/casl/issues/256) | ||
* **angular:** adds generics to CanPipe ([68bd287](https://github.com/stalniy/casl/commit/68bd287e7af165b82bbf8076ea88e83b51754a31)), closes [#256](https://github.com/stalniy/casl/issues/256) | ||
* **angular:** adds support for action only checks ([0462edb](https://github.com/stalniy/casl/commit/0462edb854ba4094e735287744404ea2d378defb)), closes [#107](https://github.com/stalniy/casl/issues/107) | ||
* **angular:** allows to use custom `Ability` instances and improves tree shaking support ([2e7a149](https://github.com/stalniy/casl/commit/2e7a1498c27d0c542e9f6507ba9b5195ae3a1da8)), closes [#249](https://github.com/stalniy/casl/issues/249) | ||
* **vue:** adds better generics typying for Vue ([5cc7b60](https://github.com/stalniy/casl/commit/5cc7b60d8a2a53db217f8ad1a4673a28f67aefce)), closes [#107](https://github.com/stalniy/casl/issues/107) | ||
### BREAKING CHANGES | ||
* **angular:** the module doesn't provide `Ability` instance anymore | ||
## [3.0.6](https://github.com/stalniy/casl/compare/@casl/angular@3.0.5...@casl/angular@3.0.6) (2020-03-06) | ||
@@ -7,0 +33,0 @@ |
@@ -1,75 +0,2 @@ | ||
import { __decorate, __metadata } from 'tslib'; | ||
import { Pipe, ChangeDetectorRef, NgModule } from '@angular/core'; | ||
import { Ability } from '@casl/ability'; | ||
var noop = function noop() {}; | ||
var ɵ0 = noop; // TODO: `pure` can be removed after https://github.com/angular/angular/issues/15041 | ||
var CanPipe = | ||
/** @class */ | ||
function () { | ||
function CanPipe(ability, cd) { | ||
this.ability = ability; | ||
this.cd = cd; | ||
this.unsubscribeFromAbility = noop; | ||
} | ||
CanPipe.prototype.transform = function (resource, action, field) { | ||
var _this = this; | ||
if (this.unsubscribeFromAbility === noop) { | ||
this.unsubscribeFromAbility = this.ability.on('updated', function () { | ||
return _this.cd.markForCheck(); | ||
}); | ||
} | ||
return this.can(action, resource, field); | ||
}; | ||
CanPipe.prototype.can = function (action, subject, field) { | ||
return this.ability.can(action, subject, field); | ||
}; | ||
CanPipe.prototype.ngOnDestroy = function () { | ||
this.unsubscribeFromAbility(); | ||
}; | ||
CanPipe = __decorate([Pipe({ | ||
name: 'can', | ||
pure: false | ||
}), __metadata("design:paramtypes", [Ability, ChangeDetectorRef])], CanPipe); | ||
return CanPipe; | ||
}(); | ||
function createAbility() { | ||
return new Ability([]); | ||
} | ||
var AbilityModule = | ||
/** @class */ | ||
function () { | ||
function AbilityModule() {} | ||
AbilityModule_1 = AbilityModule; | ||
AbilityModule.forRoot = function () { | ||
return { | ||
ngModule: AbilityModule_1, | ||
providers: [{ | ||
provide: Ability, | ||
useFactory: createAbility | ||
}] | ||
}; | ||
}; | ||
var AbilityModule_1; | ||
AbilityModule = AbilityModule_1 = __decorate([NgModule({ | ||
declarations: [CanPipe], | ||
exports: [CanPipe] | ||
})], AbilityModule); | ||
return AbilityModule; | ||
}(); | ||
export { AbilityModule, CanPipe, createAbility, ɵ0 }; | ||
import{__decorate as t,__param as n,__metadata as i}from"tslib";import{Pipe as r,Inject as o,ChangeDetectorRef as u,NgModule as e}from"@angular/core";import{PureAbility as s}from"@casl/ability";var c=function(){function t(t,n){this.t=t,this.i=n}return t.prototype.transform=function(){for(var t,n=this,i=[],r=0;r<arguments.length;r++)i[r]=arguments[r];return this.o||(this.o=this.t.on("updated",(function(){return n.i.markForCheck()}))),(t=this.t).can.apply(t,i)},t.prototype.ngOnDestroy=function(){this.o&&this.o()},t}(),f=function(){function e(t,n){this.pipe=new c(t,n)}return e.prototype.transform=function(t,n,i){return this.pipe.transform(n,t,i)},e.prototype.ngOnDestroy=function(){this.pipe.ngOnDestroy()},e=t([r({name:"can",pure:!1}),n(0,o(s)),i("design:paramtypes",[Object,u])],e)}(),a=function(){function e(t,n){this.pipe=new c(t,n)}return e.prototype.transform=function(){for(var t,n=[],i=0;i<arguments.length;i++)n[i]=arguments[i];return(t=this.pipe).transform.apply(t,n)},e.prototype.ngOnDestroy=function(){this.pipe.ngOnDestroy()},e=t([r({name:"able",pure:!1}),n(0,o(s)),i("design:paramtypes",[Object,u])],e)}(),h=function(){function n(){}return n=t([e({declarations:[f,a],exports:[f,a]})],n)}();export{h as AbilityModule,a as AblePipe,f as CanPipe}; | ||
//# sourceMappingURL=index.js.map |
@@ -1,60 +0,2 @@ | ||
import { __decorate, __metadata } from 'tslib'; | ||
import { Pipe, ChangeDetectorRef, NgModule } from '@angular/core'; | ||
import { Ability } from '@casl/ability'; | ||
const noop = () => {}; | ||
const ɵ0 = noop; // TODO: `pure` can be removed after https://github.com/angular/angular/issues/15041 | ||
let CanPipe = class CanPipe { | ||
constructor(ability, cd) { | ||
this.ability = ability; | ||
this.cd = cd; | ||
this.unsubscribeFromAbility = noop; | ||
} | ||
transform(resource, action, field) { | ||
if (this.unsubscribeFromAbility === noop) { | ||
this.unsubscribeFromAbility = this.ability.on('updated', () => this.cd.markForCheck()); | ||
} | ||
return this.can(action, resource, field); | ||
} | ||
can(action, subject, field) { | ||
return this.ability.can(action, subject, field); | ||
} | ||
ngOnDestroy() { | ||
this.unsubscribeFromAbility(); | ||
} | ||
}; | ||
CanPipe = __decorate([Pipe({ | ||
name: 'can', | ||
pure: false | ||
}), __metadata("design:paramtypes", [Ability, ChangeDetectorRef])], CanPipe); | ||
var AbilityModule_1; | ||
function createAbility() { | ||
return new Ability([]); | ||
} | ||
let AbilityModule = AbilityModule_1 = class AbilityModule { | ||
static forRoot() { | ||
return { | ||
ngModule: AbilityModule_1, | ||
providers: [{ | ||
provide: Ability, | ||
useFactory: createAbility | ||
}] | ||
}; | ||
} | ||
}; | ||
AbilityModule = AbilityModule_1 = __decorate([NgModule({ | ||
declarations: [CanPipe], | ||
exports: [CanPipe] | ||
})], AbilityModule); | ||
export { AbilityModule, CanPipe, createAbility, ɵ0 }; | ||
import{__decorate as t,__param as s,__metadata as r}from"tslib";import{Pipe as e,Inject as i,ChangeDetectorRef as n,NgModule as a}from"@angular/core";import{PureAbility as o}from"@casl/ability";class c{constructor(t,s){this.t=t,this.s=s}transform(...t){return this.i||(this.i=this.t.on("updated",()=>this.s.markForCheck())),this.t.can(...t)}ngOnDestroy(){this.i&&this.i()}}let h=class{constructor(t,s){this.pipe=new c(t,s)}transform(t,s,r){return this.pipe.transform(s,t,r)}ngOnDestroy(){this.pipe.ngOnDestroy()}};h=t([e({name:"can",pure:!1}),s(0,i(o)),r("design:paramtypes",[Object,n])],h);let l=class{constructor(t,s){this.pipe=new c(t,s)}transform(...t){return this.pipe.transform(...t)}ngOnDestroy(){this.pipe.ngOnDestroy()}};l=t([e({name:"able",pure:!1}),s(0,i(o)),r("design:paramtypes",[Object,n])],l);let m=class{};m=t([a({declarations:[h,l],exports:[h,l]})],m);export{m as AbilityModule,l as AblePipe,h as CanPipe}; | ||
//# sourceMappingURL=index.js.map |
import { ChangeDetectorRef, PipeTransform } from '@angular/core'; | ||
import { Ability } from '@casl/ability'; | ||
export declare class CanPipe implements PipeTransform { | ||
protected ability: Ability; | ||
protected cd: ChangeDetectorRef; | ||
protected unsubscribeFromAbility: Function; | ||
constructor(ability: Ability, cd: ChangeDetectorRef); | ||
transform(resource: any, action: string, field?: string): boolean; | ||
can(action: string, subject: any, field?: string): boolean; | ||
import { Unsubscribe, AnyAbility } from '@casl/ability'; | ||
declare class AbilityPipe<T extends AnyAbility> { | ||
protected _unsubscribeFromAbility?: Unsubscribe; | ||
private _ability; | ||
private _cd; | ||
constructor(ability: T, cd: ChangeDetectorRef); | ||
transform(...args: Parameters<T['can']>): boolean; | ||
ngOnDestroy(): void; | ||
} | ||
export declare class CanPipe<T extends AnyAbility> implements PipeTransform { | ||
protected pipe: AbilityPipe<T>; | ||
constructor(ability: T, cd: ChangeDetectorRef); | ||
transform(subject: Parameters<T['can']>[1], action: Parameters<T['can']>[0], field?: string): boolean; | ||
ngOnDestroy(): void; | ||
} | ||
export declare class AblePipe<T extends AnyAbility> implements PipeTransform { | ||
protected pipe: AbilityPipe<T>; | ||
constructor(ability: T, cd: ChangeDetectorRef); | ||
transform(...args: Parameters<T['can']>): boolean; | ||
ngOnDestroy(): void; | ||
} | ||
export {}; |
@@ -1,1 +0,1 @@ | ||
[{"__symbolic":"module","version":4,"metadata":{"ɵ0":{"__symbolic":"error","message":"Lambda not supported","line":3,"character":13},"CanPipe":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Pipe","line":6,"character":1},"arguments":[{"name":"can","pure":false}]}],"members":{"__ctor__":[{"__symbolic":"constructor","parameters":[{"__symbolic":"reference","module":"@casl/ability","name":"Ability","line":10,"character":33},{"__symbolic":"reference","module":"@angular/core","name":"ChangeDetectorRef","line":10,"character":56}]}],"transform":[{"__symbolic":"method"}],"can":[{"__symbolic":"method"}],"ngOnDestroy":[{"__symbolic":"method"}]}}}}] | ||
[{"__symbolic":"module","version":4,"metadata":{"CanPipe":{"__symbolic":"class","arity":1,"decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Pipe","line":28,"character":1},"arguments":[{"name":"can","pure":false}]}],"members":{"__ctor__":[{"__symbolic":"constructor","parameterDecorators":[[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Inject","line":32,"character":15},"arguments":[{"__symbolic":"reference","module":"@casl/ability","name":"PureAbility","line":32,"character":22}]}],null],"parameters":[{"__symbolic":"error","message":"Could not resolve type","line":32,"character":44,"context":{"typeName":"T"}},{"__symbolic":"reference","module":"@angular/core","name":"ChangeDetectorRef","line":32,"character":51}]}],"transform":[{"__symbolic":"method"}],"ngOnDestroy":[{"__symbolic":"method"}]}},"AblePipe":{"__symbolic":"class","arity":1,"decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Pipe","line":49,"character":1},"arguments":[{"name":"able","pure":false}]}],"members":{"__ctor__":[{"__symbolic":"constructor","parameterDecorators":[[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"Inject","line":53,"character":15},"arguments":[{"__symbolic":"reference","module":"@casl/ability","name":"PureAbility","line":53,"character":22}]}],null],"parameters":[{"__symbolic":"error","message":"Could not resolve type","line":53,"character":44,"context":{"typeName":"T"}},{"__symbolic":"reference","module":"@angular/core","name":"ChangeDetectorRef","line":32,"character":51}]}],"transform":[{"__symbolic":"method"}],"ngOnDestroy":[{"__symbolic":"method"}]}}}}] |
@@ -1,6 +0,2 @@ | ||
import { ModuleWithProviders } from '@angular/core'; | ||
import { Ability } from '@casl/ability'; | ||
export declare function createAbility(): Ability; | ||
export declare class AbilityModule { | ||
static forRoot(): ModuleWithProviders<AbilityModule>; | ||
} |
@@ -1,1 +0,1 @@ | ||
[{"__symbolic":"module","version":4,"metadata":{"createAbility":{"__symbolic":"function","parameters":[],"value":{"__symbolic":"new","expression":{"__symbolic":"reference","module":"@casl/ability","name":"Ability","line":5,"character":13},"arguments":[[]]}},"AbilityModule":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"NgModule","line":8,"character":1},"arguments":[{"declarations":[{"__symbolic":"reference","module":"./can","name":"CanPipe","line":10,"character":4}],"exports":[{"__symbolic":"reference","module":"./can","name":"CanPipe","line":13,"character":4}]}]}],"statics":{"forRoot":{"__symbolic":"function","parameters":[],"value":{"ngModule":{"__symbolic":"reference","name":"AbilityModule"},"providers":[{"provide":{"__symbolic":"reference","module":"@casl/ability","name":"Ability","line":21,"character":19},"useFactory":{"__symbolic":"reference","name":"createAbility"}}]}}}}}}] | ||
[{"__symbolic":"module","version":4,"metadata":{"AbilityModule":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"NgModule","line":3,"character":1},"arguments":[{"declarations":[{"__symbolic":"reference","module":"./can","name":"CanPipe","line":5,"character":4},{"__symbolic":"reference","module":"./can","name":"AblePipe","line":6,"character":4}],"exports":[{"__symbolic":"reference","module":"./can","name":"CanPipe","line":9,"character":4},{"__symbolic":"reference","module":"./can","name":"AblePipe","line":10,"character":4}]}]}]}}}] |
@@ -1,2 +0,2 @@ | ||
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("tslib"),require("@angular/core"),require("@casl/ability")):"function"==typeof define&&define.amd?define(["exports","tslib","@angular/core","@casl/ability"],e):e(((t=t||self).casl=t.casl||{},t.casl.ng={}),t.tslib,t.ng.core,t.casl)}(this,function(t,e,i,r){"use strict";function n(){}var o=n,s=(a.prototype.transform=function(t,e,i){var r=this;return this.unsubscribeFromAbility===n&&(this.unsubscribeFromAbility=this.ability.on("updated",function(){return r.cd.markForCheck()})),this.can(e,t,i)},a.prototype.can=function(t,e,i){return this.ability.can(t,e,i)},a.prototype.ngOnDestroy=function(){this.unsubscribeFromAbility()},a=e.__decorate([i.Pipe({name:"can",pure:!1}),e.__metadata("design:paramtypes",[r.Ability,i.ChangeDetectorRef])],a));function a(t,e){this.ability=t,this.cd=e,this.unsubscribeFromAbility=n}function c(){return new r.Ability([])}var u,l=((u=b).forRoot=function(){return{ngModule:u,providers:[{provide:r.Ability,useFactory:c}]}},b=u=e.__decorate([i.NgModule({declarations:[s],exports:[s]})],b));function b(){}t.AbilityModule=l,t.CanPipe=s,t.createAbility=c,t.ɵ0=o,Object.defineProperty(t,"__esModule",{value:!0})}); | ||
!function(t,n){"object"==typeof exports&&"undefined"!=typeof module?n(exports,require("tslib"),require("@angular/core"),require("@casl/ability")):"function"==typeof define&&define.amd?define(["exports","tslib","@angular/core","@casl/ability"],n):n(((t=t||self).casl=t.casl||{},t.casl.ng={}),t.tslib,t.ng.core,t.casl)}(this,(function(t,n,e,i){"use strict";var r=function(){function t(t,n){this.t=t,this.i=n}return t.prototype.transform=function(){for(var t,n=this,e=[],i=0;i<arguments.length;i++)e[i]=arguments[i];return this.u||(this.u=this.t.on("updated",(function(){return n.i.markForCheck()}))),(t=this.t).can.apply(t,e)},t.prototype.ngOnDestroy=function(){this.u&&this.u()},t}(),u=function(){function t(t,n){this.pipe=new r(t,n)}return t.prototype.transform=function(t,n,e){return this.pipe.transform(n,t,e)},t.prototype.ngOnDestroy=function(){this.pipe.ngOnDestroy()},t=n.o([e.Pipe({name:"can",pure:!1}),n.s(0,e.Inject(i.PureAbility)),n.h("design:paramtypes",[Object,e.ChangeDetectorRef])],t)}(),o=function(){function t(t,n){this.pipe=new r(t,n)}return t.prototype.transform=function(){for(var t,n=[],e=0;e<arguments.length;e++)n[e]=arguments[e];return(t=this.pipe).transform.apply(t,n)},t.prototype.ngOnDestroy=function(){this.pipe.ngOnDestroy()},t=n.o([e.Pipe({name:"able",pure:!1}),n.s(0,e.Inject(i.PureAbility)),n.h("design:paramtypes",[Object,e.ChangeDetectorRef])],t)}(),s=function(){function t(){}return t=n.o([e.NgModule({declarations:[u,o],exports:[u,o]})],t)}();t.AbilityModule=s,t.AblePipe=o,t.CanPipe=u,Object.defineProperty(t,"l",{value:!0})})); | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "@casl/angular", | ||
"version": "3.0.6", | ||
"description": "Angular module for CASL which makes it easy to add permissions in any Angular application", | ||
"version": "4.0.0", | ||
"description": "Angular module for CASL which makes it easy to add permissions in any Angular app", | ||
"main": "dist/umd/index.js", | ||
@@ -25,4 +25,4 @@ "module": "dist/es5m/index.js", | ||
"postrollup": "rm -rf dist/$BUILD/tmp", | ||
"test": "NODE_ENV=test jest --config ../../tools/jest.config.js", | ||
"lint": "node -p -e '`Skip linting for typescript`'", | ||
"test": "NODE_ENV=test jest --config ./jest.config.js", | ||
"lint": "eslint --ext .ts,.js src/ spec/", | ||
"prerelease": "NODE_ENV=production npm run build && npm test", | ||
@@ -48,2 +48,3 @@ "release": "semantic-release -e ../../tools/semantic-release" | ||
"@angular/core": "^9.0.0", | ||
"tslib": "^1.9.0", | ||
"@casl/ability": "^2.0.0 || ^3.0.0" | ||
@@ -50,0 +51,0 @@ }, |
271
README.md
@@ -1,4 +0,4 @@ | ||
# CASL Angular [![@casl/angular NPM version](https://badge.fury.io/js/%40casl%2Fangular.svg)](https://badge.fury.io/js/%40casl%2Fangular) [![](https://img.shields.io/npm/dm/%40casl%2Fangular.svg)](https://www.npmjs.com/package/%40casl%2Fangular) [![CASL Documentation](https://img.shields.io/badge/documentation-available-brightgreen.svg)](https://stalniy.github.io/casl/) [![CASL Join the chat at https://gitter.im/stalniy-casl/casl](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/stalniy-casl/casl?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ||
# CASL Angular [![@casl/angular NPM version](https://badge.fury.io/js/%40casl%2Fangular.svg)](https://badge.fury.io/js/%40casl%2Fangular) [![](https://img.shields.io/npm/dm/%40casl%2Fangular.svg)](https://www.npmjs.com/package/%40casl%2Fangular) [![CASL Join the chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/stalniy-casl/casl) | ||
This package allows to integrate [@casl/ability][casl-ability] into [Angular][angular] application. So, you can show or hide some components, buttons, etc based on user ability to see them. | ||
This package allows to integrate `@casl/ability` with [Angular] application. It provides `AblePipe` and **deprecated** `CanPipe` to Angular templates, so you can show or hide components, buttons, etc based on user ability to see them. | ||
@@ -9,24 +9,27 @@ ## Installation | ||
npm install @casl/angular @casl/ability | ||
# or | ||
yarn add @casl/angular @casl/ability | ||
# or | ||
pnpm add @casl/angular @casl/ability | ||
``` | ||
## Getting Started | ||
## Configure AppModule | ||
### 1. Including module | ||
To add pipes into your application's templates, you need to import `AbilityModule` in your `AppModule` and | ||
This package provides `AbilityModule` module which adds `CanPipe` to templates and `Ability` instance to dependency injection container | ||
```ts @{data-filename="app.module.ts"} | ||
import { NgModule } from '@angular/core'; | ||
import { AbilityModule } from '@casl/angular'; | ||
import { Ability, PureAbility } from '@casl/ability'; | ||
```ts | ||
// app.module.ts | ||
import { NgModule } from '@angular/core' | ||
import { AbilityModule } from '@casl/angular' | ||
// ... | ||
@NgModule({ | ||
imports: [ | ||
..., | ||
AbilityModule.forRoot() | ||
// other modules | ||
AbilityModule | ||
], | ||
declarations: [...], | ||
bootstrap: [...], | ||
providers: [ | ||
{ provide: Ability, useValue: new Ability() }, | ||
{ provide: PureAbility, useExisting: Ability } | ||
] | ||
// other properties | ||
}) | ||
@@ -36,46 +39,17 @@ export class AppModule {} | ||
**Note**: make sure that you use `AbilityModule.forRoot()` in your main module (usually it's `AppModule`) and `AbilityModule` in children modules (including lazy loaded ones). | ||
The 2nd provider provides instance of `PureAbility`, so `CanPipe` and `AblePipe` can inject it later. This pipes inject `PureAbility` (not `Ability`) because this allows an application developer to decide how to configure actions, subjects and conditions. Also this is the only way to get maximum from tree shaking (e.g., if you don't need conditions you can use `PureAbility` and get rid of `sift` library). | ||
### 2. Defining Abilities | ||
> Read [CASL and TypeScript](../../advanced/typescript) to get more details about `Ability` type configuration. | ||
This module provides an empty `Ability` instance, so you either need to provide your own or update existing one. In case if you want to provide your own, just define it using `AbilityBuilder` (or whatever way you prefer): | ||
## Update Ability instance | ||
```ts | ||
// ability.ts | ||
import { AbilityBuilder } from '@casl/ability' | ||
Majority of applications that need permission checking support have something like `AuthService` or `LoginService` or `Session` service (name it as you wish) which is responsible for user login/logout functionality. Whenever user login (and logout), we need to update `Ability` instance with new rules. | ||
export const ability = AbilityBuilder.define(can => { | ||
can('read', 'all') | ||
}) | ||
``` | ||
Let's imagine that server returns user with a role on login: | ||
Later in your `AppModule` add additional provider: | ||
```ts @{data-filename="Session.ts"} | ||
import { Ability, AbilityBuilder } from '@casl/ability'; | ||
import { Injectable } from '@angular/core'; | ||
```ts | ||
import { AbilityModule } from .... | ||
.... | ||
import { Ability } from '@casl/ability' | ||
import { ability } from './ability' | ||
@NgModule({ | ||
imports: [ | ||
..., | ||
AbilityModule.forRoot() | ||
], | ||
declarations: [...], | ||
providers: [ | ||
{ provide: Ability, useValue: ability } | ||
], | ||
bootstrap: [...], | ||
}) | ||
export class AppModule {} | ||
``` | ||
Alternatively, you can just inject existing instance and update rules. | ||
Imagine that we have a `Session` service which is responsible for user login/logout functionality. Whenever user login, we need to update ability rules with rules which server returns and reset them back on logout. Lets do this: | ||
```ts | ||
// session.ts | ||
import { Ability } from '@casl/ability' | ||
@Injectable({ provideIn: 'root' }) | ||
export class Session { | ||
@@ -87,14 +61,26 @@ private token: string | ||
login(details) { | ||
return fetch('path/to/api/login', { methods: 'POST', body: JSON.stringify(details) }) | ||
const params = { method: 'POST', body: JSON.stringify(details) }; | ||
return fetch('path/to/api/login', params) | ||
.then(response => response.json()) | ||
.then(session => { | ||
this.ability.update(session.rules) | ||
this.token = session.token | ||
}) | ||
.then((session) => { | ||
this.updateAbility(session.user); | ||
this.token = session.token; | ||
}); | ||
} | ||
private updateAbility(user) { | ||
const { can, rules } = new AbilityBuilder(); | ||
if (user.role === 'admin') { | ||
can('manage', 'all'); | ||
} else { | ||
can('read', 'all'); | ||
} | ||
this.ability.update(rules); | ||
} | ||
logout() { | ||
this.token = null | ||
this.ability.update([]) | ||
// or this.ability.update([{ actions: 'read', subject: 'all' }]) to make everything to be readonly | ||
this.token = null; | ||
this.ability.update([]); | ||
} | ||
@@ -104,9 +90,48 @@ } | ||
See [@casl/ability][casl-ability] package for more information on how to define abilities. | ||
> See [Define rules](../../guide/define-rules) to get more information of how to define `Ability` | ||
### 3. Check permissions in templates | ||
Then use this `Session` service in `LoginComponent`: | ||
To check permissions in any template you can use `CanPipe`: | ||
```ts | ||
import { Component } from '@angular/core'; | ||
import { Session } from '../services/Session'; | ||
@Component({ | ||
selector: 'login-form', | ||
template: ` | ||
<form (ngSubmit)="login()"> | ||
<input type="email" [(ngModel)]="email" /> | ||
<input type="password" [(ngModel)]="password" /> | ||
<button type="submit">Login</button> | ||
</form> | ||
` | ||
}) | ||
export class LoginForm { | ||
email: string; | ||
password: string; | ||
constructor(private session: Session) {} | ||
login() { | ||
const { email, password } = this; | ||
return this.session.login({ email, password }); | ||
} | ||
} | ||
``` | ||
## Check permissions in templates | ||
To check permissions in any template you can use `AblePipe`: | ||
```html | ||
<div *ngIf="'create' | able: 'Post'"> | ||
<a (click)="createPost()">Add Post</a> | ||
</div> | ||
``` | ||
> You can read the expression in `ngIf` as "if creatable Post" | ||
Or with **deprecated** `CanPipe`: | ||
```html | ||
<div *ngIf="'Post' | can: 'create'"> | ||
@@ -117,22 +142,38 @@ <a (click)="createPost()">Add Post</a> | ||
#### Performance considerations | ||
`CanPipe` was deprecated because it is less readable and it was harder to integrate it with all type definitions supported by `Ability`'s `can` method. That's why `CanPipe` has weaker typings than `AblePipe`. | ||
Due to [open feature in Angular](https://github.com/angular/angular/issues/15041), `CanPipe` was designed to be impure. This should work pretty fine if you have simple list of rules but may become a bottleneck when you have a lot of them. | ||
Don't worry, as there are several strategies which you can pick to make it faster: | ||
## Why pipe and not directive? | ||
* use memoization (either on `Ability#can` or on `CanPipe#can` methods) | ||
* use immutable objects and overwrite existing pipe to be pure | ||
Directive cannot be used to pass values into inputs of other components. For example, we need to enable or disable a button based on user's ability to create a post. With directive we cannot do this but we can do this with pipe: | ||
```html | ||
<button [disabled]="!('create' | able: 'Post')">Add Post</button> | ||
``` | ||
To track status of directive implementation, check [#276](https://github.com/stalniy/casl/issues/276) | ||
## Performance considerations | ||
Due to [open feature in Angular](https://github.com/angular/angular/issues/15041), pipes were designed to be [impure](https://angular.io/guide/pipes#impure-pipes). This should work pretty fine for majority of cases but may become a bottleneck if you have more than 50 rules (depending on application size and computer characteristics). | ||
Don't worry, there are several strategies which you can pick to make things fast when they become slower: | ||
* use memoization, either on `Ability#can` or on `AblePipe#transform` method | ||
* if you use immutable objects, you can extend existing pipe and make it pure | ||
* use `ChangeDectionStrategy.OnPush` on your components whenever possible | ||
To memoize results of `CanPipe`, you will need to create your own one and change its `can` method to cache results (this method was specifically designed to be overloaded by child class). Also you will need to clear all memoized results when corresponding `Ability` instance is updated (see [update ability][update-ability] for details). | ||
The similar strategy can be applied to `Ability` class. Don't forget to provide new pipe or `Ability` class in Dependency injection! For example | ||
To memoize results of `AblePipe`, you will need to create your own one and change its `transform` method to cache results. Also you will need to clear all memoized results when corresponding `Ability` instance is updated. | ||
The similar strategy can be applied to `Ability` class. Don't forget to provide new pipe or `Ability` class in `AppModule`! For example | ||
```ts | ||
import { MemoizedAbility } from './ability' | ||
import { Ability } from '@casl/ability' | ||
import { NgModule } from '@angular/core'; | ||
import { Ability } from '@casl/ability'; | ||
import { MemoizedAbility } from './ability'; | ||
@NgModule({ | ||
..., | ||
// other configuration | ||
providers: [ | ||
{ provide: Ability, useClass: MemoizedAbility } | ||
{ provide: Ability, useValue: new MemoizedAbility() }, | ||
{ provide: PureAbility, useExisting: Ability }, | ||
] | ||
@@ -146,15 +187,11 @@ }) | ||
```ts | ||
// pure-can.pipe.ts | ||
import { CanPipe } from '@casl/angular' | ||
import { AblePipe } from '@casl/angular' | ||
@Pipe({ name: 'can' }) | ||
export class MyCanPipe extends CanPipe {} | ||
@Pipe({ name: 'able', pure: true }) | ||
class PureAblePipe extends AblePipe {} | ||
// app.module.ts | ||
import { MyCanPipe } from './pure-can.pipe' | ||
@NgModule({ | ||
..., | ||
// other configuration | ||
declarations: [ | ||
MyCanPipe | ||
PureAblePipe | ||
] | ||
@@ -165,6 +202,62 @@ }) | ||
## TypeScript support | ||
The package is written in TypeScript, so it will warn you about wrong usage. | ||
It may be a bit tedious to use application specific abilities in Angular app because everywhere you inject `Ability` instance you will need to import its generic parameters: | ||
```ts | ||
import { Ability } from '@casl/ability'; | ||
import { Component } from '@angular/core'; | ||
import { AppAbilities } from '../services/AppAbility'; | ||
@Component({ | ||
selector: 'todo-item' | ||
}) | ||
export class TodoItem { | ||
constructor( | ||
private ability: Ability<AppAbilities> | ||
) {} | ||
} | ||
``` | ||
To make the life easier, instead of creating a separate type you can create a separate class: | ||
```ts @{data-filename="AppAbility.ts"} | ||
import { Ability } from '@casl/ability'; | ||
type Actions = 'create' | 'read' | 'update' | 'delete'; | ||
type Subjects = 'Article' | 'User' | ||
export type AppAbilities = [Actions, Subjects]; | ||
export class AppAbility extends Ability<AppAbilities> { | ||
} | ||
``` | ||
And provide this class instead in `AppModule` providers: | ||
```ts @{data-filename="AppModule.ts"} | ||
import { NgModule } from '@angular/core'; | ||
import { Ability } from '@casl/ability'; | ||
import { AppAbility } from './services/AppAbility'; | ||
@NgModule({ | ||
// other configuration | ||
providers: [ | ||
{ provide: AppAbility, useValue: new AppAbility() }, | ||
{ provide: PureAbility, useExisting: AppAbility }, | ||
] | ||
}) | ||
export class AppModule {} | ||
``` | ||
## Want to help? | ||
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on guidelines for [contributing][contributing] | ||
Want to file a bug, contribute some code, or improve documentation? Excellent! Read up on guidelines for [contributing]. | ||
If you'd like to help us sustain our community and project, consider [to become a financial contributor on Open Collective](https://opencollective.com/casljs/contribute) | ||
> See [Support CASL](../../support) for details | ||
## License | ||
@@ -174,5 +267,3 @@ | ||
[contributing]: /CONTRIBUTING.md | ||
[angular]: https://angular.io/ | ||
[update-ability]: https://stalniy.github.io/casl/abilities/2017/07/20/define-abilities.html#update-abilities | ||
[casl-ability]: http://npmjs.com/package/@casl/ability | ||
[contributing]: https://github.com/stalniy/casl/blob/master/CONTRIBUTING.md | ||
[Angular]: https://angular.io/ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
40629
262
3
47