├── 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 |

7 |
8 |
9 | Router test links:
10 |
11 | -
12 |
17 |
18 | -
19 |
24 |
25 |
26 |
27 |
28 |
29 | Test links:
30 |
31 | -
32 |
44 |
45 | -
46 |
59 |
60 | -
61 |
66 |
67 | -
68 |
73 |
74 | -
75 |
80 |
81 |
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 |
--------------------------------------------------------------------------------