├── src ├── assets │ └── .gitkeep ├── app │ ├── app.component.css │ ├── components │ │ ├── first │ │ │ ├── first.component.css │ │ │ ├── first.component.html │ │ │ └── first.component.ts │ │ └── second │ │ │ ├── second.component.css │ │ │ ├── second.component.html │ │ │ └── second.component.ts │ ├── constants.ts │ ├── app.config.server.ts │ ├── app.routes.ts │ ├── app.config.ts │ ├── app.component.ts │ └── app.component.html ├── favicon.ico ├── styles.css ├── main.ts ├── main.server.ts └── index.html ├── projects └── ng-yandex-metrika │ ├── ng-package.json │ ├── src │ ├── public-api.ts │ └── lib │ │ ├── ng-yandex-metrika.config.ts │ │ ├── ng-yandex-metrika-goal.directive.ts │ │ ├── ng-yandex-metrika.module.ts │ │ ├── ng-yandex-metrika-config-factories.ts │ │ ├── yandex-mterika-tag.ts │ │ └── ng-yandex-metrika.service.ts │ ├── tsconfig.lib.json │ ├── tsconfig.lib.prod.json │ ├── package.json │ └── README.md ├── tsconfig.spec.json ├── .editorconfig ├── README.md ├── tsconfig.app.json ├── .gitignore ├── tsconfig.json ├── package.json ├── server.ts └── angular.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/first/first.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/second/second.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/components/first/first.component.html: -------------------------------------------------------------------------------- 1 |

First

2 | -------------------------------------------------------------------------------- /src/app/components/second/second.component.html: -------------------------------------------------------------------------------- 1 |

Second

2 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FriOne/ng-yandex-metrika/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /src/app/constants.ts: -------------------------------------------------------------------------------- 1 | export const METRIKA_ID_1 = 45631461; 2 | export const METRIKA_ID_2 = 93202589; 3 | -------------------------------------------------------------------------------- /projects/ng-yandex-metrika/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/ng-yandex-metrika", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /projects/ng-yandex-metrika/src/public-api.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/ng-yandex-metrika-goal.directive'; 2 | export * from './lib/ng-yandex-metrika.service'; 3 | export * from './lib/ng-yandex-metrika-config-factories'; 4 | export * from './lib/ng-yandex-metrika.module'; 5 | -------------------------------------------------------------------------------- /projects/ng-yandex-metrika/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "inlineSources": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { appConfig } from './app/app.config'; 3 | import { AppComponent } from './app/app.component'; 4 | 5 | bootstrapApplication(AppComponent, appConfig) 6 | .catch((err) => console.error(err)); 7 | -------------------------------------------------------------------------------- /src/main.server.ts: -------------------------------------------------------------------------------- 1 | import { bootstrapApplication } from '@angular/platform-browser'; 2 | import { AppComponent } from './app/app.component'; 3 | import { config } from './app/app.config.server'; 4 | 5 | const bootstrap = () => bootstrapApplication(AppComponent, config); 6 | 7 | export default bootstrap; 8 | -------------------------------------------------------------------------------- /src/app/components/first/first.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-first', 5 | standalone: true, 6 | imports: [], 7 | templateUrl: './first.component.html', 8 | styleUrl: './first.component.css' 9 | }) 10 | export class FirstComponent { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /projects/ng-yandex-metrika/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.lib.json", 4 | "compilerOptions": { 5 | "declarationMap": false 6 | }, 7 | "angularCompilerOptions": { 8 | "compilationMode": "partial" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/app/components/second/second.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-second', 5 | standalone: true, 6 | imports: [], 7 | templateUrl: './second.component.html', 8 | styleUrl: './second.component.css' 9 | }) 10 | export class SecondComponent { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NgYandexMetrikaNew 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NgYandexMetrikaNew 2 | 3 | This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 17.0.6. 4 | 5 | ## Test 6 | 7 | Run these commands to start dev server: 8 | 9 | ```bash 10 | ng build ng-yandex-metrika 11 | ng serve 12 | ``` 13 | 14 | After that go to http://localhost:4200/?_ym_debug=1 and click on links to see if console outputs yandex metrika events. 15 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [ 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/main.ts", 12 | "src/main.server.ts", 13 | "server.ts" 14 | ], 15 | "include": [ 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /src/app/app.config.server.ts: -------------------------------------------------------------------------------- 1 | import { mergeApplicationConfig, ApplicationConfig } from '@angular/core'; 2 | import { provideServerRendering } from '@angular/platform-server'; 3 | import { appConfig } from './app.config'; 4 | 5 | const serverConfig: ApplicationConfig = { 6 | providers: [ 7 | provideServerRendering() 8 | ] 9 | }; 10 | 11 | export const config = mergeApplicationConfig(appConfig, serverConfig); 12 | -------------------------------------------------------------------------------- /src/app/app.routes.ts: -------------------------------------------------------------------------------- 1 | import { Routes } from '@angular/router'; 2 | 3 | import { FirstComponent } from './components/first/first.component'; 4 | import { SecondComponent } from './components/second/second.component'; 5 | 6 | export const routes: Routes = [ 7 | { path: 'first', component: FirstComponent }, 8 | { path: 'second', component: SecondComponent }, 9 | { path: '**', component: FirstComponent }, 10 | ]; 11 | -------------------------------------------------------------------------------- /projects/ng-yandex-metrika/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-yandex-metrika", 3 | "version": "18.0.0", 4 | "description": "Yandex metrika for angular", 5 | "keywords": [ 6 | "yandex", 7 | "metrika", 8 | "ng", 9 | "angular" 10 | ], 11 | "author": "Lyubimov Roman", 12 | "license": "MIT", 13 | "repository": { 14 | "url": "git@github.com:FriOne/ng-yandex-metrika.git" 15 | }, 16 | "peerDependencies": { 17 | "@angular/common": "^18.0.0", 18 | "@angular/compiler": "^18.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /projects/ng-yandex-metrika/src/lib/ng-yandex-metrika.config.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken } from '@angular/core'; 2 | 3 | import { InitParameters } from './yandex-mterika-tag'; 4 | 5 | export interface CounterConfig extends InitParameters { 6 | id: number; 7 | } 8 | 9 | export const DEFAULT_COUNTER_ID = new InjectionToken('DEFAULT_COUNTER_ID'); 10 | export const YANDEX_COUNTERS_CONFIGS = new InjectionToken('YANDEX_COUNTERS_CONFIGS'); 11 | export const ALTERNATIVE_URL = new InjectionToken('ALTERNATIVE_URL'); 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .vscode/ 17 | .project 18 | .classpath 19 | .c9/ 20 | *.launch 21 | .settings/ 22 | *.sublime-workspace 23 | 24 | # Visual Studio Code 25 | .vscode/* 26 | !.vscode/settings.json 27 | !.vscode/tasks.json 28 | !.vscode/launch.json 29 | !.vscode/extensions.json 30 | .history/* 31 | 32 | # Miscellaneous 33 | /.angular/cache 34 | .sass-cache/ 35 | /connect.lock 36 | /coverage 37 | /libpeerconnection.log 38 | testem.log 39 | /typings 40 | 41 | # System files 42 | .DS_Store 43 | Thumbs.db 44 | -------------------------------------------------------------------------------- /src/app/app.config.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationConfig, importProvidersFrom } from '@angular/core'; 2 | import { provideRouter } from '@angular/router'; 3 | import { provideClientHydration } from '@angular/platform-browser'; 4 | import { MetrikaModule } from 'ng-yandex-metrika'; 5 | 6 | import { METRIKA_ID_1, METRIKA_ID_2 } from './constants'; 7 | import { routes } from './app.routes'; 8 | 9 | export const appConfig: ApplicationConfig = { 10 | providers: [ 11 | provideRouter(routes), 12 | provideClientHydration(), 13 | importProvidersFrom( 14 | MetrikaModule.forRoot([ 15 | { id: METRIKA_ID_1, webvisor: true }, 16 | { id: METRIKA_ID_2 }, 17 | ], { 18 | defaultCounter: 1, 19 | alternativeUrl: 'https://cdn.jsdelivr.net/npm/yandex-metrica-watch/tag.js' 20 | }) 21 | ), 22 | ] 23 | }; 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "outDir": "./dist/out-tsc", 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "noImplicitOverride": true, 9 | "noPropertyAccessFromIndexSignature": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "skipLibCheck": true, 13 | "paths": { 14 | "ng-yandex-metrika": [ 15 | "./dist/ng-yandex-metrika" 16 | ] 17 | }, 18 | "esModuleInterop": true, 19 | "sourceMap": true, 20 | "declaration": false, 21 | "experimentalDecorators": true, 22 | "moduleResolution": "node", 23 | "importHelpers": true, 24 | "target": "ES2022", 25 | "module": "ES2022", 26 | "useDefineForClassFields": false, 27 | "lib": [ 28 | "ES2022", 29 | "dom" 30 | ] 31 | }, 32 | "angularCompilerOptions": { 33 | "enableI18nLegacyMessageIdFormat": false, 34 | "strictInjectionParameters": true, 35 | "strictInputAccessModifiers": true, 36 | "strictTemplates": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /projects/ng-yandex-metrika/src/lib/ng-yandex-metrika-goal.directive.ts: -------------------------------------------------------------------------------- 1 | import { AfterViewInit, Directive, ElementRef, Input, OnDestroy, Renderer2 } from '@angular/core'; 2 | 3 | import { Metrika } from './ng-yandex-metrika.service'; 4 | 5 | @Directive({ 6 | selector: '[metrikaGoal]', 7 | standalone: true, 8 | }) 9 | export class MetrikaGoalDirective implements AfterViewInit, OnDestroy { 10 | @Input() goalName!: string; 11 | @Input() eventName = 'click'; 12 | @Input() params!: Record; 13 | @Input() counterId?: number; 14 | @Input() callback!: () => void; 15 | 16 | private removeEventListener!: () => void; 17 | 18 | constructor( 19 | private metrika: Metrika, 20 | private renderer: Renderer2, 21 | private el: ElementRef 22 | ) {} 23 | 24 | ngAfterViewInit() { 25 | try { 26 | this.removeEventListener = this.renderer.listen(this.el.nativeElement, this.eventName, () => { 27 | const options = { callback: this.callback, ...this.params }; 28 | 29 | this.metrika.reachGoal(this.goalName, options, undefined, undefined, this.counterId); 30 | }); 31 | } catch (err) { 32 | console.error(err); 33 | } 34 | } 35 | 36 | ngOnDestroy() { 37 | if (this.removeEventListener) { 38 | this.removeEventListener(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-yandex-metrika-proj", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "start": "ng serve", 7 | "build": "ng build", 8 | "watch": "ng build --watch --configuration development", 9 | "test": "ng test", 10 | "serve:ssr:ng-yandex-metrika-proj": "node dist/ng-yandex-metrika-proj/server/server.mjs" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "^18.1.0", 15 | "@angular/common": "^18.1.0", 16 | "@angular/compiler": "^18.1.0", 17 | "@angular/core": "^18.1.0", 18 | "@angular/forms": "^18.1.0", 19 | "@angular/platform-browser": "^18.1.0", 20 | "@angular/platform-browser-dynamic": "^18.1.0", 21 | "@angular/platform-server": "^18.1.0", 22 | "@angular/router": "^18.1.0", 23 | "@angular/ssr": "^18.1.0", 24 | "express": "^4.18.2", 25 | "rxjs": "~7.8.0", 26 | "tslib": "^2.3.0", 27 | "zone.js": "~0.14.2" 28 | }, 29 | "devDependencies": { 30 | "@angular-devkit/build-angular": "^18.1.0", 31 | "@angular/cli": "^18.1.0", 32 | "@angular/compiler-cli": "^18.1.0", 33 | "@types/express": "^4.17.17", 34 | "@types/jasmine": "~5.1.0", 35 | "@types/node": "^18.18.0", 36 | "jasmine-core": "~5.1.0", 37 | "karma": "~6.4.0", 38 | "karma-chrome-launcher": "~3.2.0", 39 | "karma-coverage": "~2.2.0", 40 | "karma-jasmine": "~5.1.0", 41 | "karma-jasmine-html-reporter": "~2.1.0", 42 | "ng-packagr": "^18.1.0", 43 | "typescript": "~5.5.3" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /projects/ng-yandex-metrika/src/lib/ng-yandex-metrika.module.ts: -------------------------------------------------------------------------------- 1 | import { APP_INITIALIZER, Injector, ModuleWithProviders, NgModule } from '@angular/core'; 2 | import { PLATFORM_ID } from '@angular/core'; 3 | 4 | import { Metrika } from './ng-yandex-metrika.service'; 5 | import { 6 | ALTERNATIVE_URL, 7 | CounterConfig, 8 | DEFAULT_COUNTER_ID, 9 | YANDEX_COUNTERS_CONFIGS, 10 | } from './ng-yandex-metrika.config'; 11 | import { appInitializerFactory, defineDefaultId } from './ng-yandex-metrika-config-factories'; 12 | 13 | type Options = { 14 | defaultCounter?: number; 15 | alternativeUrl?: string; 16 | }; 17 | 18 | @NgModule() 19 | export class MetrikaModule { 20 | static forRoot(configs: CounterConfig | CounterConfig[], options: Options = {}): ModuleWithProviders { 21 | const { defaultCounter, alternativeUrl } = options; 22 | 23 | return { 24 | ngModule: MetrikaModule, 25 | providers: [ 26 | { 27 | provide: DEFAULT_COUNTER_ID, 28 | useValue: defineDefaultId(configs, defaultCounter), 29 | }, 30 | { 31 | provide: YANDEX_COUNTERS_CONFIGS, 32 | useValue: Array.isArray(configs) ? configs : [configs], 33 | }, 34 | { 35 | provide: ALTERNATIVE_URL, 36 | useValue: alternativeUrl, 37 | }, 38 | { 39 | provide: APP_INITIALIZER, 40 | useFactory: appInitializerFactory, 41 | deps: [YANDEX_COUNTERS_CONFIGS, PLATFORM_ID, ALTERNATIVE_URL], 42 | multi: true, 43 | }, 44 | { 45 | provide: Metrika, 46 | useClass: Metrika, 47 | deps: [Injector, PLATFORM_ID], 48 | }, 49 | ], 50 | }; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject, PLATFORM_ID } from '@angular/core'; 2 | import { Metrika, MetrikaGoalDirective } from 'ng-yandex-metrika'; 3 | import { METRIKA_ID_2 } from './constants'; 4 | import { NavigationEnd, Router, RouterLink, RouterOutlet } from '@angular/router'; 5 | import { isPlatformServer, Location } from '@angular/common'; 6 | import { filter } from 'rxjs/operators'; 7 | 8 | @Component({ 9 | selector: 'app-root', 10 | templateUrl: './app.component.html', 11 | styleUrls: ['./app.component.css'], 12 | imports: [MetrikaGoalDirective, RouterLink, RouterOutlet], 13 | standalone: true 14 | }) 15 | export class AppComponent { 16 | title = 'Angular Yandex Metrika'; 17 | 18 | constructor( 19 | private metrika: Metrika, 20 | private router: Router, 21 | location: Location, 22 | @Inject(PLATFORM_ID) platformId: Object, 23 | ) { 24 | if (isPlatformServer(platformId)) { 25 | return; 26 | } 27 | 28 | let prevPath = location.path(); 29 | this.router 30 | .events 31 | .pipe(filter(event => (event instanceof NavigationEnd))) 32 | .subscribe(() => { 33 | const newPath = location.path(); 34 | this.metrika.hit(newPath, { 35 | referer: prevPath, 36 | callback: () => { console.log('hit end'); } 37 | }); 38 | prevPath = newPath; 39 | }); 40 | } 41 | 42 | onLinkClick() { 43 | this.metrika.reachGoal('test'); 44 | } 45 | 46 | async onExternalLinkClick(event: MouseEvent) { 47 | if (event.currentTarget instanceof HTMLAnchorElement) { 48 | const options = { 49 | callback: () => {}, 50 | ctx: this, 51 | params: { title: 'test title' }, 52 | }; 53 | 54 | await this.metrika.extLink(event.currentTarget.href, options, METRIKA_ID_2); 55 | console.log('link clicked'); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /server.ts: -------------------------------------------------------------------------------- 1 | import { APP_BASE_HREF } from '@angular/common'; 2 | import { CommonEngine } from '@angular/ssr'; 3 | import express from 'express'; 4 | import { fileURLToPath } from 'node:url'; 5 | import { dirname, join, resolve } from 'node:path'; 6 | import bootstrap from './src/main.server'; 7 | 8 | // The Express app is exported so that it can be used by serverless Functions. 9 | export function app(): express.Express { 10 | const server = express(); 11 | const serverDistFolder = dirname(fileURLToPath(import.meta.url)); 12 | const browserDistFolder = resolve(serverDistFolder, '../browser'); 13 | const indexHtml = join(serverDistFolder, 'index.server.html'); 14 | 15 | const commonEngine = new CommonEngine(); 16 | 17 | server.set('view engine', 'html'); 18 | server.set('views', browserDistFolder); 19 | 20 | // Example Express Rest API endpoints 21 | // server.get('/api/**', (req, res) => { }); 22 | // Serve static files from /browser 23 | server.get('*.*', express.static(browserDistFolder, { 24 | maxAge: '1y' 25 | })); 26 | 27 | // All regular routes use the Angular engine 28 | server.get('*', (req, res, next) => { 29 | const { protocol, originalUrl, baseUrl, headers } = req; 30 | 31 | commonEngine 32 | .render({ 33 | bootstrap, 34 | documentFilePath: indexHtml, 35 | url: `${protocol}://${headers.host}${originalUrl}`, 36 | publicPath: browserDistFolder, 37 | providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }], 38 | }) 39 | .then((html) => res.send(html)) 40 | .catch((err) => next(err)); 41 | }); 42 | 43 | return server; 44 | } 45 | 46 | function run(): void { 47 | const port = process.env['PORT'] || 4000; 48 | 49 | // Start up the Node server 50 | const server = app(); 51 | server.listen(port, () => { 52 | console.log(`Node Express server listening on http://localhost:${port}`); 53 | }); 54 | } 55 | 56 | run(); 57 | -------------------------------------------------------------------------------- /projects/ng-yandex-metrika/src/lib/ng-yandex-metrika-config-factories.ts: -------------------------------------------------------------------------------- 1 | import { isPlatformBrowser } from '@angular/common'; 2 | 3 | import { CounterConfig } from './ng-yandex-metrika.config'; 4 | 5 | export function defineDefaultId(counterConfigs: CounterConfig | CounterConfig[], defaultCounter?: number) { 6 | let configs: CounterConfig[]; 7 | if (counterConfigs instanceof Array) { 8 | configs = counterConfigs; 9 | } else { 10 | configs = [counterConfigs as CounterConfig]; 11 | } 12 | let defaultId: number; 13 | 14 | if (!defaultCounter) { 15 | defaultId = configs[0].id; 16 | } else if (defaultCounter < configs.length) { 17 | defaultId = configs[defaultCounter].id; 18 | } else { 19 | defaultId = defaultCounter; 20 | } 21 | 22 | if (!defaultId) { 23 | console.warn('You provided wrong counter id as a default:', defaultCounter); 24 | return; 25 | } 26 | 27 | let defaultCounterExists = false; 28 | let config; 29 | for (let i = 0; i < configs.length; i++) { 30 | config = configs[i]; 31 | if (!config.id) { 32 | console.warn('You should provide counter id to use Yandex metrika counter', config); 33 | continue; 34 | } 35 | if (config.id === defaultId) { 36 | defaultCounterExists = true; 37 | } 38 | } 39 | 40 | if (!defaultCounterExists) { 41 | console.warn('You provided wrong counter id as a default:', defaultCounter); 42 | } 43 | return defaultId; 44 | } 45 | 46 | export function appInitializerFactory(counterConfigs: CounterConfig[], platformId: Object, alternativeUrl?: string) { 47 | if (isPlatformBrowser(platformId)) { 48 | return insertMetrika.bind(null, counterConfigs, alternativeUrl); 49 | } 50 | 51 | return () => 'none'; 52 | } 53 | 54 | function insertMetrika(counterConfigs: CounterConfig[], alternativeUrl?: string) { 55 | window.ym = window.ym || function() { 56 | (window.ym.a = window.ym.a || []).push(arguments) 57 | }; 58 | window.ym.l = new Date().getTime(); 59 | 60 | const lastScript = document.getElementsByTagName('script')[0]; 61 | const metrikaScript = document.createElement('script'); 62 | metrikaScript.type = 'text/javascript'; 63 | metrikaScript.src = alternativeUrl ?? 'https://mc.yandex.ru/metrika/tag.js'; 64 | metrikaScript.async = true; 65 | lastScript.parentNode!.insertBefore(metrikaScript, lastScript); 66 | 67 | for (const { id, ...counterConfig } of counterConfigs) { 68 | window.ym(id, 'init', counterConfig); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

4 | Welcome to {{ title }}! 5 |

6 | Angular Logo 7 |
8 | 9 |

Router test links:

10 | 26 | 27 | 28 | 29 |

Test links:

30 | 82 | -------------------------------------------------------------------------------- /projects/ng-yandex-metrika/README.md: -------------------------------------------------------------------------------- 1 | # Angular Yandex Metrika 2 | Модуль добавляет на страницу счетчик(и) яндекс метрики, доступны все [методы](https://yandex.ru/support/metrika/objects/method-reference.xml) API метрики. 3 | Для методов, в которые можно передать колбэк, возвращается промис, но колбэки так же работают. 4 | 5 | ```bash 6 | npm install ng-yandex-metrika 7 | ``` 8 | 9 | Чтобы подключить, нужно добавить скрипт в шаблон, либо подключить с помощью загрузчика модулей, и подключить в приложение. 10 | ```typescript 11 | import { MetrikaModule } from 'ng-yandex-metrika'; 12 | 13 | @NgModule({ 14 | imports: [ 15 | MetrikaModule.forRoot( 16 | { id: 35567075, webvisor: true }, // CounterConfig | CounterConfig[] 17 | { 18 | // Можно задать ID счетчика, либо порядковый номер в массиве, необязательный параметр, по умолчанию первый 19 | defaultCounter, 20 | // Для загрузки метрики с другого источника 21 | alternativeUrl: 'https://cdn.jsdelivr.net/npm/yandex-metrica-watch/tag.js', 22 | }, 23 | ), 24 | ] 25 | }) 26 | ``` 27 | ```typescript 28 | import { ApplicationConfig, importProvidersFrom } from '@angular/core'; 29 | 30 | export const appConfig: ApplicationConfig = { 31 | providers: [ 32 | // ... 33 | importProvidersFrom( 34 | MetrikaModule.forRoot([ 35 | { id: 35567075, webvisor: true }, 36 | { id: 35567076 }, 37 | ]) 38 | ), 39 | ] 40 | }; 41 | 42 | ``` 43 | 44 | Если вам нужно, чтобы счетчик работал без javascript, нужно добавить это: 45 | ```html 46 | 47 | ``` 48 | 49 | Для отправки javascript цели можно вызвать метод вручную: 50 | ```typescript 51 | export class AppComponent { 52 | constructor(private metrika: Metrika) {} 53 | 54 | onClick() { 55 | this.metrika.reachGoal('a_goal_name'); 56 | } 57 | } 58 | ``` 59 | 60 | Или использовать директиву: 61 | ```html 62 | 63 | 64 | 65 | ``` 66 | 67 | Для отправки данных о просмотре: 68 | ```typescript 69 | import { NavigationEnd, Router, RouterLink, RouterOutlet } from '@angular/router'; 70 | import { Location } from '@angular/common'; 71 | import { filter } from 'rxjs/operators'; 72 | 73 | export class AppComponent { 74 | constructor( 75 | private metrika: Metrika, 76 | private router: Router, 77 | location: Location, 78 | @Inject(PLATFORM_ID) platformId: Object, 79 | ) { 80 | if (isPlatformServer(platformId)) { 81 | return; 82 | } 83 | 84 | let prevPath = location.path(); 85 | this.router 86 | .events 87 | .pipe(filter(event => (event instanceof NavigationEnd))) 88 | .subscribe(() => { 89 | const newPath = location.path(); 90 | this.metrika.hit(newPath, { 91 | referer: prevPath, 92 | callback: () => { console.log('hit end'); } 93 | }); 94 | prevPath = newPath; 95 | }); 96 | } 97 | } 98 | ``` 99 | -------------------------------------------------------------------------------- /projects/ng-yandex-metrika/src/lib/yandex-mterika-tag.ts: -------------------------------------------------------------------------------- 1 | declare global { 2 | interface Window { 3 | ym: YandexEvent; 4 | } 5 | } 6 | 7 | export interface YandexEvent { 8 | (counterId: number, eventName: "init", parameters: InitParameters): void; 9 | (counterId: number, eventName: "addFileExtension", extensions: string | string[]): void; 10 | // eslint-disable-next-line @definitelytyped/no-unnecessary-generics 11 | (counterId: number, eventName: "extLink", url: string, options?: ExtLinkOptions): void; 12 | // eslint-disable-next-line @definitelytyped/no-unnecessary-generics 13 | (counterId: number, eventName: "file", url: string, options?: FileOptions): void; 14 | (counterId: number, eventName: "getClientID", cb: (clientID: string) => void): void; 15 | // eslint-disable-next-line @definitelytyped/no-unnecessary-generics 16 | (counterId: number, eventName: "hit", url: string, options?: HitOptions): void; 17 | /** @deprecated */ 18 | ( 19 | counterId: number, 20 | eventName: "hit", 21 | url: string, 22 | title?: string, 23 | referer?: string, 24 | params?: VisitParameters, 25 | ): void; 26 | // eslint-disable-next-line @definitelytyped/no-unnecessary-generics 27 | (counterId: number, eventName: "notBounce", options?: NotBounceOptions): void; 28 | (counterId: number, eventName: "params", parameters: VisitParameters | VisitParameters[]): void; 29 | ( 30 | counterId: number, 31 | eventName: "reachGoal", 32 | target: string, 33 | params?: VisitParameters, 34 | callback?: (this: CTX) => void, 35 | ctx?: CTX, 36 | ): void; 37 | (counterId: number, eventName: "replacePhones"): void; 38 | (counterId: number, eventName: "setUserID", userID: string): void; 39 | (counterId: number, eventName: "userParams", parameters: UserParameters): void; 40 | 41 | l: number; 42 | a: unknown[]; 43 | } 44 | 45 | export interface VisitParameters { 46 | order_price?: number | undefined; 47 | currency?: string | undefined; 48 | [key: string]: any; 49 | } 50 | 51 | export interface UserParameters { 52 | UserID?: number | undefined; 53 | [key: string]: any; 54 | } 55 | 56 | export interface InitParameters { 57 | accurateTrackBounce?: boolean | number | undefined; 58 | childIframe?: boolean | undefined; 59 | clickmap?: boolean | undefined; 60 | defer?: boolean | undefined; 61 | ecommerce?: boolean | string | any[] | undefined; 62 | params?: VisitParameters | VisitParameters[] | undefined; 63 | userParams?: UserParameters | undefined; 64 | trackHash?: boolean | undefined; 65 | trackLinks?: boolean | undefined; 66 | trustedDomains?: string[] | undefined; 67 | type?: number | undefined; 68 | ut?: "noindex" | undefined; 69 | webvisor?: boolean | undefined; 70 | triggerEvent?: boolean | undefined; 71 | } 72 | 73 | export interface CallbackOptions { 74 | callback?: (this: CTX) => void; 75 | ctx?: CTX | undefined; 76 | } 77 | 78 | export interface ExtLinkOptions extends CallbackOptions { 79 | params?: VisitParameters | undefined; 80 | title?: string | undefined; 81 | } 82 | 83 | export interface FileOptions extends CallbackOptions { 84 | params?: VisitParameters | undefined; 85 | referer?: string | undefined; 86 | title?: string | undefined; 87 | } 88 | 89 | export interface HitOptions extends CallbackOptions { 90 | params?: VisitParameters | undefined; 91 | referer?: string | undefined; 92 | title?: string | undefined; 93 | } 94 | 95 | export interface NotBounceOptions extends CallbackOptions {} 96 | 97 | -------------------------------------------------------------------------------- /projects/ng-yandex-metrika/src/lib/ng-yandex-metrika.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Injector } from '@angular/core'; 2 | 3 | import { DEFAULT_COUNTER_ID, YANDEX_COUNTERS_CONFIGS, CounterConfig } from './ng-yandex-metrika.config'; 4 | import { 5 | CallbackOptions, 6 | ExtLinkOptions, 7 | FileOptions, 8 | HitOptions, 9 | NotBounceOptions, 10 | UserParameters, 11 | VisitParameters 12 | } from './yandex-mterika-tag'; 13 | 14 | @Injectable({ 15 | providedIn: 'root' 16 | }) 17 | export class Metrika { 18 | private defaultCounterId: number; 19 | private counterConfigs: CounterConfig[]; 20 | 21 | constructor(injector: Injector) { 22 | this.defaultCounterId = injector.get(DEFAULT_COUNTER_ID); 23 | this.counterConfigs = injector.get(YANDEX_COUNTERS_CONFIGS); 24 | } 25 | 26 | addFileExtension(extensions: string | string[], counterId?: number) { 27 | window.ym(counterId ?? this.defaultCounterId, 'addFileExtension', extensions); 28 | } 29 | 30 | extLink(url: string, options: ExtLinkOptions = {}, counterId?: number) { 31 | const promise = this.getCallbackPromise(options); 32 | 33 | window.ym(counterId ?? this.defaultCounterId, 'extLink', url, options); 34 | 35 | return promise; 36 | } 37 | 38 | file(url: string, options: FileOptions = {}, counterId?: number) { 39 | const promise = this.getCallbackPromise(options); 40 | 41 | window.ym(counterId ?? this.defaultCounterId, 'file', url, options); 42 | 43 | return promise; 44 | } 45 | 46 | getClientID(counterId?: number) { 47 | return new Promise((resolve) => { 48 | window.ym(counterId ?? this.defaultCounterId, 'getClientID', resolve); 49 | }); 50 | } 51 | 52 | setUserID(userId: string, counterId?: number) { 53 | window.ym(counterId ?? this.defaultCounterId, 'setUserID', userId); 54 | } 55 | 56 | userParams(parameters: UserParameters, counterId?: number) { 57 | window.ym(counterId ?? this.defaultCounterId, 'userParams', parameters); 58 | } 59 | 60 | params(parameters: VisitParameters | VisitParameters[], counterId?: number) { 61 | window.ym(counterId ?? this.defaultCounterId, 'params', parameters); 62 | } 63 | 64 | replacePhones(counterId?: number) { 65 | window.ym(counterId ?? this.defaultCounterId, 'replacePhones'); 66 | } 67 | 68 | async notBounce(options: NotBounceOptions, counterId?: number) { 69 | const promise = this.getCallbackPromise(options); 70 | 71 | window.ym(counterId ?? this.defaultCounterId, 'notBounce', options); 72 | 73 | return promise; 74 | } 75 | 76 | fireEvent = this.reachGoal; 77 | reachGoal( 78 | target: string, 79 | params: VisitParameters | undefined = undefined, 80 | callback: (this: CTX) => void = () => {}, 81 | ctx: CTX | undefined = undefined, 82 | counterId?: number 83 | ) { 84 | const options = { callback, ctx }; 85 | const promise = this.getCallbackPromise(options); 86 | 87 | window.ym( 88 | counterId ?? this.defaultCounterId, 89 | 'reachGoal', 90 | target, 91 | params, 92 | options.callback, 93 | options.ctx 94 | ); 95 | 96 | return promise; 97 | } 98 | 99 | hit(url: string, options: HitOptions = {}, counterId?: number) { 100 | const promise = this.getCallbackPromise(options); 101 | 102 | window.ym(counterId ?? this.defaultCounterId, 'hit', url, options); 103 | 104 | return promise; 105 | } 106 | 107 | private getCallbackPromise(options: CallbackOptions) { 108 | return new Promise((resolve) => { 109 | const optionsCallback = options.callback; 110 | options.callback = function() { 111 | if (optionsCallback) { 112 | optionsCallback.call(this); 113 | } 114 | resolve(this); 115 | }; 116 | }); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "ng-yandex-metrika-proj": { 7 | "projectType": "application", 8 | "schematics": {}, 9 | "root": "", 10 | "sourceRoot": "src", 11 | "prefix": "app", 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:application", 15 | "options": { 16 | "outputPath": "dist/ng-yandex-metrika-proj", 17 | "index": "src/index.html", 18 | "browser": "src/main.ts", 19 | "polyfills": [ 20 | "zone.js" 21 | ], 22 | "tsConfig": "tsconfig.app.json", 23 | "assets": [ 24 | "src/favicon.ico", 25 | "src/assets" 26 | ], 27 | "styles": [ 28 | "src/styles.css" 29 | ], 30 | "scripts": [], 31 | "server": "src/main.server.ts", 32 | "prerender": true, 33 | "ssr": { 34 | "entry": "server.ts" 35 | } 36 | }, 37 | "configurations": { 38 | "production": { 39 | "budgets": [ 40 | { 41 | "type": "initial", 42 | "maximumWarning": "500kb", 43 | "maximumError": "1mb" 44 | }, 45 | { 46 | "type": "anyComponentStyle", 47 | "maximumWarning": "2kb", 48 | "maximumError": "4kb" 49 | } 50 | ], 51 | "outputHashing": "all" 52 | }, 53 | "development": { 54 | "optimization": false, 55 | "extractLicenses": false, 56 | "sourceMap": true 57 | } 58 | }, 59 | "defaultConfiguration": "production" 60 | }, 61 | "serve": { 62 | "builder": "@angular-devkit/build-angular:dev-server", 63 | "configurations": { 64 | "production": { 65 | "buildTarget": "ng-yandex-metrika-proj:build:production" 66 | }, 67 | "development": { 68 | "buildTarget": "ng-yandex-metrika-proj:build:development" 69 | } 70 | }, 71 | "defaultConfiguration": "development" 72 | }, 73 | "extract-i18n": { 74 | "builder": "@angular-devkit/build-angular:extract-i18n", 75 | "options": { 76 | "buildTarget": "ng-yandex-metrika-proj:build" 77 | } 78 | } 79 | } 80 | }, 81 | "ng-yandex-metrika": { 82 | "projectType": "library", 83 | "root": "projects/ng-yandex-metrika", 84 | "sourceRoot": "projects/ng-yandex-metrika/src", 85 | "prefix": "lib", 86 | "schematics": { 87 | "@schematics/angular:component": { 88 | "flat": true, 89 | "inlineStyle": true, 90 | "inlineTemplate": true, 91 | "standalone": true, 92 | "style": "none" 93 | }, 94 | "@schematics/angular:directive": { 95 | "flat": true, 96 | "standalone": true 97 | }, 98 | "@schematics/angular:pipe": { 99 | "standalone": true 100 | }, 101 | "@schematics/angular:service": { 102 | "flat": true 103 | } 104 | }, 105 | "architect": { 106 | "build": { 107 | "builder": "@angular-devkit/build-angular:ng-packagr", 108 | "options": { 109 | "project": "projects/ng-yandex-metrika/ng-package.json" 110 | }, 111 | "configurations": { 112 | "production": { 113 | "tsConfig": "projects/ng-yandex-metrika/tsconfig.lib.prod.json" 114 | }, 115 | "development": { 116 | "tsConfig": "projects/ng-yandex-metrika/tsconfig.lib.json" 117 | } 118 | }, 119 | "defaultConfiguration": "production" 120 | } 121 | } 122 | } 123 | } 124 | } 125 | --------------------------------------------------------------------------------