Skip to content

Commit 145b047

Browse files
Alexthekiba
authored andcommitted
feat(core): added *cookies directive
CookiesDirective provides tempalte composition An Example: <ng-container *cookies="let cookie set 'ngxf' value 'Best Of The Best' path '/' domain: 'localhost' expires '01 Oct 2020'"> {{ cookie }} </ng-container> <ng-container *cookies="let cookie get 'ngxf'"> {{ cookie }} </ng-container> <ng-container *cookies="let cookie remove 'ngxf' path '/' domain: 'localhost'"> {{ cookie }} </ng-container>
1 parent 69dd2a9 commit 145b047

6 files changed

Lines changed: 319 additions & 18 deletions

File tree

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import { Directive, Input, TemplateRef, ViewContainerRef, EmbeddedViewRef, OnChanges, SimpleChanges } from '@angular/core';
2+
import { CookieOptionsArgs, CookiesService } from '../tools/cookies.tools';
3+
4+
enum CookiesStrategies {
5+
GET = 'get',
6+
SET = 'set',
7+
REMOVE = 'remove'
8+
}
9+
10+
interface CookiesContext {
11+
$implicit: any;
12+
data: any;
13+
}
14+
15+
interface CookiesStrategy {
16+
type: CookiesStrategies;
17+
changes: string[];
18+
require: string[];
19+
}
20+
21+
const COOKIES_CONFIG: CookiesStrategy[] = [
22+
{
23+
type: CookiesStrategies.GET,
24+
changes: ['cookiesGet'],
25+
require: ['cookiesGet']
26+
},
27+
{
28+
type: CookiesStrategies.SET,
29+
changes: [
30+
'cookiesSet',
31+
'cookiesValue',
32+
'cookiesPath',
33+
'cookiesDomain',
34+
'cookiesExpires',
35+
'cookiesSecure'
36+
],
37+
require: ['cookiesSet']
38+
},
39+
{
40+
type: CookiesStrategies.REMOVE,
41+
changes: ['cookiesRemove', 'cookiesPath', 'cookiesDomain'],
42+
require: ['cookiesRemove']
43+
},
44+
];
45+
46+
47+
@Directive({ selector: '[cookies]' })
48+
export class CookiesDirective implements OnChanges {
49+
50+
@Input() private cookiesGet: string;
51+
@Input() private cookiesSet: string;
52+
@Input() private cookiesRemove: string;
53+
@Input() private cookiesValue: any;
54+
55+
@Input() private cookiesPath: string;
56+
@Input() private cookiesDomain: string;
57+
@Input() private cookiesExpires: string | Date;
58+
@Input() private cookiesSecure: boolean;
59+
60+
private context: CookiesContext = {
61+
$implicit: null,
62+
get data() {
63+
return this.$implicit;
64+
}
65+
};
66+
67+
private viewRef: EmbeddedViewRef<CookiesContext> =
68+
this.viewContainerRef.createEmbeddedView(this.templateRef, this.context);
69+
70+
constructor(
71+
private templateRef: TemplateRef<CookiesContext>,
72+
private viewContainerRef: ViewContainerRef,
73+
private cookiesService: CookiesService
74+
) {
75+
}
76+
77+
ngOnChanges(changes: SimpleChanges) {
78+
const strategy: CookiesStrategy = this.findStrategy(changes);
79+
80+
if (strategy) {
81+
this.execute(strategy);
82+
}
83+
}
84+
85+
private findStrategy(changes: SimpleChanges): CookiesStrategy {
86+
return COOKIES_CONFIG.find((strategy) => {
87+
return strategy.changes.some(field => !!changes[field])
88+
&& strategy.require.every(field => !!this[field]);
89+
});
90+
}
91+
92+
private execute(strategy: CookiesStrategy): void {
93+
const options = strategy.changes.map(field => this[field]);
94+
95+
this.action(strategy.type, ...options);
96+
}
97+
98+
private action(type: string, ...options: string[]): void {
99+
const name = options[0];
100+
const value = options[1];
101+
const cookieOptions: CookieOptionsArgs = {
102+
path: options[2],
103+
domain: options[3],
104+
expires: options[4],
105+
secure: !!options[5]
106+
};
107+
108+
if (CookiesStrategies.GET === type) {
109+
this.context.$implicit = this.getCookie(name);
110+
}
111+
112+
if (CookiesStrategies.SET === type) {
113+
this.setCookie(name, value, cookieOptions);
114+
this.context.$implicit = value;
115+
}
116+
117+
if (CookiesStrategies.REMOVE === type) {
118+
this.removeCookie(name, cookieOptions);
119+
this.context.$implicit = undefined;
120+
}
121+
122+
this.viewRef.markForCheck();
123+
}
124+
125+
private getCookie(name: string): string {
126+
return this.cookiesService.get(name);
127+
}
128+
129+
private setCookie(name: string, value: string, options?: CookieOptionsArgs): void {
130+
this.cookiesService.set(name, value, options);
131+
}
132+
private removeCookie(name: string, options?: CookieOptionsArgs): void {
133+
this.cookiesService.remove(name, options);
134+
}
135+
}

projects/platform/src/lib/directives/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,21 @@ import { RouteDirective } from './route.directive';
33
import { InitDirective } from './init.directive';
44
import { TimeoutDirective } from './timeout.directive';
55
import { ComposeDirective, ReturnDirective } from './compose.directive';
6+
import { CookiesDirective } from './cookies.directive';
67

78
export const DIRECTIVES = [
89
HttpDirective,
910
RouteDirective,
1011
InitDirective,
1112
TimeoutDirective,
1213
ComposeDirective,
13-
ReturnDirective
14+
ReturnDirective,
15+
CookiesDirective
1416
];
1517

1618
export * from './http.directive';
1719
export * from './route.directive';
1820
export * from './init.directive';
1921
export * from './timeout.directive';
2022
export * from './compose.directive';
23+
export * from './cookies.directive';
Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,28 @@
11
import { NgModule } from '@angular/core';
22
import {
3-
HttpDirective,
4-
RouteDirective,
5-
InitDirective,
6-
TimeoutDirective,
7-
ComposeDirective,
8-
ReturnDirective
3+
HttpDirective,
4+
RouteDirective,
5+
InitDirective,
6+
TimeoutDirective,
7+
ComposeDirective,
8+
ReturnDirective,
9+
CookiesDirective
910
} from './directives';
1011

1112
const DIRECTIVES = [
12-
HttpDirective,
13-
RouteDirective,
14-
InitDirective,
15-
TimeoutDirective,
16-
ComposeDirective,
17-
ReturnDirective
13+
HttpDirective,
14+
RouteDirective,
15+
InitDirective,
16+
TimeoutDirective,
17+
ComposeDirective,
18+
ReturnDirective,
19+
CookiesDirective
1820
];
1921

2022
@NgModule({
21-
imports: [],
22-
declarations: [ DIRECTIVES ],
23-
exports: [ DIRECTIVES ]
23+
imports: [],
24+
declarations: [DIRECTIVES],
25+
exports: [DIRECTIVES]
2426
})
25-
export class NgxfModule { }
27+
export class NgxfModule {
28+
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { Inject, Injectable, Optional } from '@angular/core';
2+
import { APP_BASE_HREF } from '@angular/common';
3+
4+
export interface CookieOptionsArgs {
5+
path?: string;
6+
domain?: string;
7+
expires?: string | Date;
8+
secure?: boolean;
9+
}
10+
11+
export interface ICookies {
12+
[key: string]: string[];
13+
}
14+
15+
export interface ICookieService {
16+
get(key: string): string;
17+
set(key: string, value: string, options?: CookieOptionsArgs): void;
18+
remove(key: string, options?: CookieOptionsArgs): void;
19+
}
20+
21+
export class CookieOptions {
22+
23+
public path: string;
24+
public domain: string;
25+
public expires: string | Date;
26+
public secure: boolean;
27+
28+
constructor({ path, domain, expires, secure }: CookieOptionsArgs = {}) {
29+
this.path = this.isPresent(path) ? path : null;
30+
this.domain = this.isPresent(domain) ? domain : null;
31+
this.expires = this.isPresent(expires) ? expires : null;
32+
this.secure = this.isPresent(secure) ? secure : false;
33+
}
34+
35+
public merge(options?: CookieOptionsArgs): CookieOptions {
36+
return new CookieOptions(<CookieOptionsArgs>{
37+
path: this.isPresent(options) && this.isPresent(options.path) ? options.path : this.path,
38+
domain: this.isPresent(options) && this.isPresent(options.domain) ? options.domain : this.domain,
39+
expires: this.isPresent(options) && this.isPresent(options.expires) ? options.expires : this.expires,
40+
secure: this.isPresent(options) && this.isPresent(options.secure) ? options.secure : this.secure,
41+
});
42+
}
43+
44+
private isPresent(obj: any): boolean {
45+
return obj !== undefined && obj !== null;
46+
}
47+
}
48+
49+
@Injectable({
50+
providedIn: 'root'
51+
})
52+
export class BaseCookieOptions extends CookieOptions {
53+
constructor(@Optional() @Inject(APP_BASE_HREF) private baseHref: string) {
54+
super({ path: baseHref || '/' });
55+
}
56+
}
57+
58+
@Injectable({
59+
providedIn: 'root'
60+
})
61+
export class CookiesService implements ICookieService {
62+
63+
constructor(
64+
@Optional() private defaultOptions?: CookieOptions
65+
) {
66+
}
67+
68+
protected get cookieString(): string {
69+
return document.cookie || '';
70+
}
71+
72+
protected set cookieString(val: string) {
73+
document.cookie = val;
74+
}
75+
76+
private cookieReader(key: string): string {
77+
const currentCookieString = this.cookieString;
78+
79+
if (currentCookieString) {
80+
const cookieArray = currentCookieString.split('; ');
81+
82+
return cookieArray.reduce((cookies: ICookies, current: string) => {
83+
const cookie = current.split('=');
84+
85+
return { ...cookies, [cookie[0]]: decodeURIComponent(cookie[1]) };
86+
}, {})[key];
87+
}
88+
}
89+
90+
private cookieWriter(name: string, value: string, options?: CookieOptionsArgs) {
91+
this.cookieString = this.buildCookieString(name, value, options);
92+
}
93+
94+
private buildCookieString(name: string, value: string, options?: CookieOptionsArgs): string {
95+
const defaultOpts = this.defaultOptions || new CookieOptions(<CookieOptionsArgs>{ path: '/' });
96+
const opts: CookieOptions = this.mergeOptions(defaultOpts, options);
97+
98+
let expires = opts.expires;
99+
100+
if (!value) {
101+
expires = 'Thu, 01 Jan 1970 00:00:00 GMT';
102+
value = '';
103+
}
104+
105+
if (typeof expires === 'string') {
106+
expires = new Date(expires);
107+
}
108+
109+
let str = encodeURIComponent(name) + '=' + encodeURIComponent(value);
110+
111+
str += opts.path ? `;path=${opts.path}` : '';
112+
str += opts.domain ? `;domain=${opts.domain}` : '';
113+
str += expires ? `;expires=${expires.toUTCString()}` : '';
114+
str += opts.secure ? ';secure' : '';
115+
116+
const cookieLength = str.length + 1;
117+
118+
if (cookieLength > 4096) {
119+
console.log(`Cookie \'${name}\' possibly not set or overflowed because it was too large (${cookieLength} > 4096 bytes)!`);
120+
}
121+
122+
return str;
123+
}
124+
125+
private mergeOptions(defaultOpts: CookieOptions, providedOpts?: CookieOptionsArgs): CookieOptions {
126+
const newOpts = defaultOpts;
127+
128+
if (providedOpts) {
129+
return newOpts.merge(new CookieOptions(providedOpts));
130+
}
131+
132+
return newOpts;
133+
}
134+
135+
public get(key: string): string {
136+
return this.cookieReader(key);
137+
}
138+
139+
public set(key: string, value: string, options?: CookieOptionsArgs): void {
140+
this.cookieWriter(key, value, options);
141+
}
142+
143+
public remove(key: string, options?: CookieOptionsArgs): void {
144+
this.cookieWriter(key, undefined, options);
145+
}
146+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './cookies.tools';

src/app/app.component.html

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,15 @@
11
<!--The content below is only a placeholder and can be replaced.-->
2-
<router-outlet></router-outlet>
2+
<!--<router-outlet></router-outlet>-->
3+
4+
<ng-container *cookies="let cookie set 'ngxf' value 'Best Of The Best' path '/' domain: 'localhost' expires '01 Oct 2020'">
5+
{{ cookie }}
6+
</ng-container>
7+
8+
<ng-container *cookies="let cookie get 'ngxf'">
9+
{{ cookie }}
10+
</ng-container>
11+
12+
13+
<ng-container *cookies="let cookie remove 'ngxf' path '/' domain: 'localhost'">
14+
{{ cookie }}
15+
</ng-container>

0 commit comments

Comments
 (0)