├── .nvmrc
├── src
├── assets
│ └── .npmignore
├── favicon.ico
├── styles.css
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── lib
│ ├── ng-package.json
│ ├── public_api.ts
│ ├── adsense.module.ts
│ ├── package.json
│ ├── adsense-config.ts
│ ├── ng2-adsense.spec.ts
│ └── adsense.component.ts
├── app
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── page.component.ts
│ ├── app.component.html
│ └── page.component.spec.ts
├── main.ts
├── test.ts
├── index.html
└── polyfills.ts
├── postcss.config.js
├── .codecov.yml
├── .prettierrc
├── tailwind.config.js
├── .editorconfig
├── tsconfig.app.json
├── tsconfig.spec.json
├── .gitignore
├── .eslintrc.json
├── tsconfig.json
├── LICENSE
├── karma.conf.js
├── .github
└── workflows
│ └── ci.yml
├── package.json
├── README.md
└── angular.json
/.nvmrc:
--------------------------------------------------------------------------------
1 | 20
2 |
--------------------------------------------------------------------------------
/src/assets/.npmignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scttcper/ng2-adsense/HEAD/src/favicon.ico
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true
3 | };
4 |
--------------------------------------------------------------------------------
/src/lib/ng-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "lib": {
3 | "entryFile": "public_api.ts"
4 | },
5 | "dest": "../../dist"
6 | }
7 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/.codecov.yml:
--------------------------------------------------------------------------------
1 | coverage:
2 | range: "50..100"
3 | status:
4 | project: false
5 | patch: false
6 | comment:
7 | require_changes: true
8 | behavior: once
9 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "trailingComma": "all",
5 | "bracketSpacing": true,
6 | "printWidth": 100,
7 | "arrowParens": "avoid"
8 | }
9 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ['./src/**/*.{html,ts}'],
4 | theme: {
5 | extend: {},
6 | },
7 | plugins: [],
8 | };
9 |
--------------------------------------------------------------------------------
/src/lib/public_api.ts:
--------------------------------------------------------------------------------
1 | export { AdsenseComponent } from './adsense.component';
2 | export { AdsenseModule } from './adsense.module';
3 | export { AdsenseConfig, ADSENSE_TOKEN } from './adsense-config';
4 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, VERSION } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'ng2-root',
5 | templateUrl: './app.component.html',
6 | })
7 | export class AppComponent {
8 | version = VERSION;
9 | }
10 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 | },
8 | "files": [
9 | "src/main.ts",
10 | "src/polyfills.ts"
11 | ],
12 | "include": [
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/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 | "files": [
11 | "src/test.ts",
12 | "src/polyfills.ts"
13 | ],
14 | "include": [
15 | "src/**/*.spec.ts",
16 | "src/**/*.d.ts"
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false
8 | };
9 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic()
12 | .bootstrapModule(AppModule)
13 | .catch(err => console.error(err));
14 |
--------------------------------------------------------------------------------
/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js/testing';
4 | import { getTestBed } from '@angular/core/testing';
5 | import {
6 | BrowserDynamicTestingModule,
7 | platformBrowserDynamicTesting
8 | } from '@angular/platform-browser-dynamic/testing';
9 |
10 | // First, initialize the Angular testing environment.
11 | getTestBed().initTestEnvironment(
12 | BrowserDynamicTestingModule,
13 | platformBrowserDynamicTesting(),
14 | { teardown: { destroyAfterEach: true }},
15 | );
16 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | ng2-adsense
10 |
11 |
12 |
13 |
14 |
15 | Loading...
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/lib/adsense.module.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ModuleWithProviders, NgModule } from '@angular/core';
3 |
4 | import { AdsenseConfig, ADSENSE_TOKEN } from './adsense-config';
5 | import { AdsenseComponent } from './adsense.component';
6 |
7 | @NgModule({
8 | imports: [CommonModule],
9 | exports: [AdsenseComponent],
10 | declarations: [AdsenseComponent],
11 | })
12 | export class AdsenseModule {
13 | static forRoot(config: Partial = {}): ModuleWithProviders {
14 | return {
15 | ngModule: AdsenseModule,
16 | providers: [{ provide: ADSENSE_TOKEN, useValue: config }],
17 | };
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.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 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.angular/cache
29 | /.sass-cache
30 | /connect.lock
31 | /coverage
32 | /libpeerconnection.log
33 | npm-debug.log
34 | testem.log
35 | /typings
36 | yarn-error.log
37 |
38 | # e2e
39 | /e2e/*.js
40 | /e2e/*.map
41 |
42 | # System Files
43 | .DS_Store
44 | Thumbs.db
45 | /deploy
46 | /waste
47 | yarn.lock
48 |
--------------------------------------------------------------------------------
/src/lib/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "../../node_modules/ng-packagr/package.schema.json",
3 | "name": "ng2-adsense",
4 | "version": "0.0.0-placeholder",
5 | "description": "Adsense for Angular",
6 | "repository": "scttcper/ng2-adsense",
7 | "keywords": ["Angular", "ng2", "angular2", "typescript", "AdSense"],
8 | "license": "MIT",
9 | "bugs": {
10 | "url": "https://github.com/scttcper/ng2-adsense/issues"
11 | },
12 | "homepage": "https://github.com/scttcper/ng2-adsense",
13 | "peerDependencies": {
14 | "@angular/core": ">=17.0.0-0",
15 | "@angular/common": ">=17.0.0-0"
16 | },
17 | "publishConfig": {
18 | "access": "public",
19 | "provenance": true
20 | },
21 | "release": {
22 | "branches": [
23 | "master"
24 | ]
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": [
4 | "projects/**/*"
5 | ],
6 | "overrides": [
7 | {
8 | "files": [
9 | "*.ts"
10 | ],
11 | "parserOptions": {
12 | "project": [
13 | "tsconfig.json",
14 | "e2e/tsconfig.json"
15 | ],
16 | "createDefaultProgram": true
17 | },
18 | "extends": [
19 | "plugin:@angular-eslint/recommended",
20 | "plugin:@angular-eslint/template/process-inline-templates"
21 | ],
22 | "rules": {
23 | "@angular-eslint/component-class-suffix": "off"
24 | }
25 | },
26 | {
27 | "files": [
28 | "*.html"
29 | ],
30 | "extends": [
31 | "plugin:@angular-eslint/template/recommended"
32 | ],
33 | "rules": {}
34 | }
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitReturns": true,
10 | "noFallthroughCasesInSwitch": true,
11 | "sourceMap": true,
12 | "declaration": false,
13 | "downlevelIteration": true,
14 | "experimentalDecorators": true,
15 | "moduleResolution": "node",
16 | "importHelpers": true,
17 | "target": "ES2022",
18 | "module": "es2020",
19 | "lib": [
20 | "es2022",
21 | "dom"
22 | ],
23 | "useDefineForClassFields": false
24 | },
25 | "angularCompilerOptions": {
26 | "strictInjectionParameters": true,
27 | "strictTemplates": true
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/lib/adsense-config.ts:
--------------------------------------------------------------------------------
1 | import { InjectionToken } from '@angular/core';
2 |
3 | /**
4 | * Set optional global default values
5 | */
6 | export interface AdsenseConfig {
7 | /** adsense account ca-pub-XXXXXXXXXXXXXXXX */
8 | adClient: string;
9 | /** ad slot/number */
10 | adSlot: string | number;
11 | /** data-ad-format default: auto */
12 | adFormat: string;
13 | /** ins element display style */
14 | display: string;
15 | /** ins element height in px */
16 | width: number;
17 | /** ins element width in px */
18 | height: number;
19 | /** used for in-feed ads */
20 | layout: string;
21 | /** used for in-feed ads */
22 | layoutKey: string;
23 | /** enable page-level ads */
24 | pageLevelAds: boolean;
25 | adtest: string;
26 | /** used for flexible ads */
27 | fullWidthResponsive: boolean;
28 | }
29 |
30 | export const ADSENSE_TOKEN = new InjectionToken(
31 | 'AdsenseConfig',
32 | );
33 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { BrowserModule } from '@angular/platform-browser';
3 | import { RouterModule, Routes } from '@angular/router';
4 |
5 | import { AdsenseModule } from '../lib/public_api';
6 | import { AppComponent } from './app.component';
7 | import {
8 | OtherPageComponent,
9 | PageComponent,
10 | ReloadPageComponent,
11 | } from './page.component';
12 |
13 | const routes: Routes = [
14 | { path: '', component: PageComponent },
15 | { path: '2', component: OtherPageComponent },
16 | { path: 'ads/:id', component: ReloadPageComponent },
17 | { path: '**', redirectTo: '', pathMatch: 'full' },
18 | ];
19 |
20 | @NgModule({
21 | declarations: [
22 | AppComponent,
23 | PageComponent,
24 | OtherPageComponent,
25 | ReloadPageComponent,
26 | ],
27 | imports: [
28 | BrowserModule,
29 | RouterModule.forRoot(routes, { useHash: true }),
30 | AdsenseModule.forRoot({
31 | adClient: 'ca-pub-7640562161899788',
32 | adSlot: 2930227358,
33 | }),
34 | ],
35 | bootstrap: [AppComponent],
36 | })
37 | export class AppModule {}
38 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Scott Cooper
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('karma-jasmine'),
10 | require('karma-chrome-launcher'),
11 | require('karma-jasmine-html-reporter'),
12 | require('karma-coverage-istanbul-reporter'),
13 | require('@angular-devkit/build-angular/plugins/karma'),
14 | ],
15 | client: {
16 | clearContext: false, // leave Jasmine Spec Runner output visible in browser
17 | },
18 | coverageIstanbulReporter: {
19 | dir: require('path').join(__dirname, './coverage/zzz'),
20 | reports: ['html', 'lcovonly', 'text-summary'],
21 | fixWebpackSourcePaths: true,
22 | },
23 | reporters: ['coverage-istanbul', 'progress', 'kjhtml'],
24 | port: 9876,
25 | colors: true,
26 | logLevel: config.LOG_INFO,
27 | autoWatch: true,
28 | browsers: ['Chrome'],
29 | customLaunchers: {
30 | ChromeCI: {
31 | base: 'ChromeHeadless',
32 | flags: ['--no-sandbox', '--disable-gpu'],
33 | },
34 | },
35 | singleRun: false,
36 | restartOnFileChange: true,
37 | });
38 | };
39 |
--------------------------------------------------------------------------------
/src/lib/ng2-adsense.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, waitForAsync } from '@angular/core/testing';
2 |
3 | import { AdsenseComponent } from './adsense.component';
4 | import { AdsenseModule } from './adsense.module';
5 |
6 | describe('AdsenseComponent', () => {
7 | const options = {
8 | adClient: 'ca-pub-7640562161899788',
9 | adSlot: 2930227358,
10 | layout: 'z1',
11 | };
12 | beforeEach(waitForAsync(() => {
13 | TestBed.configureTestingModule({
14 | imports: [AdsenseModule.forRoot(options)],
15 | }).compileComponents();
16 | }));
17 |
18 | it('should render ng adsense', waitForAsync(() => {
19 | const fixture = TestBed.createComponent(AdsenseComponent);
20 | fixture.detectChanges();
21 | const compiled = fixture.debugElement.nativeElement;
22 | const ad = compiled.querySelector('ins');
23 | expect(ad.className).toContain('adsbygoogle');
24 | expect(ad.getAttribute('data-ad-slot')).toEqual(String(options.adSlot));
25 | expect(ad.getAttribute('data-ad-client')).toEqual(options.adClient);
26 | expect(ad.getAttribute('data-layout')).toEqual('z1');
27 | expect(ad.getAttribute('data-layout-key')).toEqual(null);
28 | expect(ad.getAttribute('height')).toEqual(null);
29 | expect(ad.getAttribute('width')).toEqual(null);
30 | expect(ad.getAttribute('data-full-width-responsive')).toEqual(null);
31 | }));
32 | });
33 |
--------------------------------------------------------------------------------
/src/app/page.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ActivatedRoute } from '@angular/router';
3 |
4 | /**
5 | * Uses global values
6 | */
7 | @Component({
8 | selector: 'ng2-page-1',
9 | template: `
10 |
11 | Current View: {{ title }}
12 |
13 |
14 |
15 | `,
16 | })
17 | export class PageComponent {
18 | title = 'Page 1';
19 | }
20 |
21 | /**
22 | * Uses local ad values
23 | */
24 | @Component({
25 | selector: 'ng2-page-2',
26 | template: `
27 |
28 | Current View: {{ title }}
29 |
30 |
33 |
34 |
37 |
38 | `,
39 | })
40 | export class OtherPageComponent extends PageComponent {
41 | title = 'Page 2';
42 | }
43 |
44 | /**
45 | * Refreshes ads on params changes
46 | */
47 | @Component({
48 | selector: 'ng2-page-3',
49 | template: `
50 |
51 | Current View: {{ title }}
52 |
53 |
54 |
55 | `,
56 | })
57 | export class ReloadPageComponent implements OnInit {
58 | title = 'Page 3';
59 | loading = true;
60 |
61 | constructor(private route: ActivatedRoute) {}
62 |
63 | ngOnInit(): void {
64 | this.route.params.subscribe(params => {
65 | this.title = `Page ${params.id}`;
66 | this.loading = true;
67 | setTimeout(() => (this.loading = false), 200);
68 | });
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 |
15 | - uses: pnpm/action-setup@v3
16 | with:
17 | version: 9
18 |
19 | - uses: actions/setup-node@v4
20 | with:
21 | node-version: 20
22 | cache: "pnpm"
23 |
24 | - name: install
25 | run: pnpm install
26 |
27 | - name: lint
28 | run: pnpm run lint
29 |
30 | - run: npm run build
31 |
32 | - name: test
33 | run: npm run test:ci
34 |
35 | - name: coverage
36 | uses: codecov/codecov-action@v4
37 | with:
38 | token: ${{ secrets.CODECOV_TOKEN }}
39 |
40 | publish:
41 | needs: build
42 | runs-on: ubuntu-latest
43 | if: github.ref_name == 'master'
44 | permissions:
45 | contents: write # to be able to publish a GitHub release
46 | issues: write # to be able to comment on released issues
47 | pull-requests: write # to be able to comment on released pull requests
48 | id-token: write # to enable use of OIDC for npm provenance
49 | steps:
50 | - uses: actions/checkout@v4
51 | with:
52 | fetch-depth: 0
53 |
54 | - uses: pnpm/action-setup@v3
55 | with:
56 | version: 9
57 |
58 | - uses: actions/setup-node@v4
59 | with:
60 | node-version: 20
61 | cache: "pnpm"
62 |
63 | - name: install
64 | run: pnpm install
65 |
66 | - run: npm run build
67 |
68 | - name: release
69 | run: cd dist && npx semantic-release
70 | env:
71 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
72 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
73 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ng2-adsense",
3 | "version": "0.0.0-placeholder",
4 | "license": "MIT",
5 | "repository": "scttcper/ng2-adsense",
6 | "private": true,
7 | "scripts": {
8 | "ng": "ng",
9 | "start": "ng serve",
10 | "build": "ng-packagr -p src/lib/ng-package.json",
11 | "postbuild": "cpy README.md LICENSE dist",
12 | "ghpages": "ng build --configuration production --no-progress",
13 | "test": "ng test --no-watch --browsers=ChromeCI",
14 | "test:watch": "ng test --browsers=ChromeCI",
15 | "test:ci": "ng test --watch=false --code-coverage --no-progress --browsers=ChromeCI",
16 | "lint": "ng lint",
17 | "lint:fix": "ng lint --fix"
18 | },
19 | "devDependencies": {
20 | "@angular-devkit/build-angular": "17.3.5",
21 | "@angular-eslint/builder": "17.3.0",
22 | "@angular-eslint/eslint-plugin": "17.3.0",
23 | "@angular-eslint/eslint-plugin-template": "17.3.0",
24 | "@angular-eslint/template-parser": "17.3.0",
25 | "@angular/animations": "17.3.5",
26 | "@angular/cli": "17.3.5",
27 | "@angular/common": "17.3.5",
28 | "@angular/compiler": "17.3.5",
29 | "@angular/compiler-cli": "17.3.5",
30 | "@angular/core": "17.3.5",
31 | "@angular/forms": "17.3.5",
32 | "@angular/language-service": "17.3.5",
33 | "@angular/platform-browser": "17.3.5",
34 | "@angular/platform-browser-dynamic": "17.3.5",
35 | "@angular/router": "17.3.5",
36 | "@types/jasmine": "5.1.4",
37 | "@types/node": "20.12.7",
38 | "@typescript-eslint/eslint-plugin": "7.7.0",
39 | "@typescript-eslint/parser": "7.7.0",
40 | "autoprefixer": "10.4.19",
41 | "core-js": "3.33.0",
42 | "cpy-cli": "5.0.0",
43 | "eslint": "8.57.0",
44 | "eslint-plugin-import": "2.29.1",
45 | "eslint-plugin-jsdoc": "48.2.3",
46 | "eslint-plugin-prefer-arrow": "1.2.3",
47 | "jasmine-core": "5.1.2",
48 | "karma": "6.4.3",
49 | "karma-chrome-launcher": "3.2.0",
50 | "karma-cli": "2.0.0",
51 | "karma-coverage-istanbul-reporter": "3.0.3",
52 | "karma-jasmine": "5.1.0",
53 | "karma-jasmine-html-reporter": "2.1.0",
54 | "ng-packagr": "17.3.0",
55 | "postcss": "^8.4.38",
56 | "puppeteer": "^21.3.6",
57 | "rxjs": "7.8.1",
58 | "tailwindcss": "^3.4.3",
59 | "tslib": "2.6.2",
60 | "typescript": "5.4.5",
61 | "zone.js": "0.14.4"
62 | },
63 | "engines": {
64 | "node": ">=18",
65 | "pnpm": ">=9"
66 | },
67 | "release": {
68 | "branches": [
69 | "master"
70 | ]
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/guide/browser-support
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /**
22 | * By default, zone.js will patch all possible macroTask and DomEvents
23 | * user can disable parts of macroTask/DomEvents patch by setting following flags
24 | * because those flags need to be set before `zone.js` being loaded, and webpack
25 | * will put import in the top of bundle, so user need to create a separate file
26 | * in this directory (for example: zone-flags.ts), and put the following flags
27 | * into that file, and then add the following code before importing zone.js.
28 | * import './zone-flags';
29 | *
30 | * The flags allowed in zone-flags.ts are listed here.
31 | *
32 | * The following flags will work for all browsers.
33 | *
34 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
35 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
36 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
37 | *
38 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
39 | * with the following flag, it will bypass `zone.js` patch for IE/Edge
40 | *
41 | * (window as any).__Zone_enable_cross_context_check = true;
42 | *
43 | */
44 |
45 | /***************************************************************************************************
46 | * Zone JS is required by default for Angular itself.
47 | */
48 | import 'zone.js'; // Included with Angular CLI.
49 |
50 |
51 | /***************************************************************************************************
52 | * APPLICATION IMPORTS
53 | */
54 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | ng2-adsense
Angular AdSense Component
6 |
7 |
8 | Shows AdSense ad loading on route change. Sometimes a blank or identical AdSense ad is
9 | displayed. Disable Adblock for demo.
10 |
11 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
Page 1 - responsive ads
26 | Page 2 - ads with specified width
27 |
28 | Page 3 and 4 - same component but with setTimeout when the params are changed
29 | to refresh the ads
30 |
31 |
32 |
33 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ng2-adsense [![NPM version][npm-image]][npm-url][![coverage status][coverage-img]][coverage-url]
2 |
3 | [npm-image]: https://img.shields.io/npm/v/ng2-adsense.svg
4 | [npm-url]: https://npmjs.org/package/ng2-adsense
5 | [coverage-img]: https://codecov.io/gh/scttcper/ng2-adsense/branch/master/graph/badge.svg
6 | [coverage-url]: https://codecov.io/gh/scttcper/ng2-adsense
7 |
8 | > Easy AdSense for Angular Applications
9 |
10 | **Demo**: https://ng2-adsense.xmplaylist.com/
11 |
12 | ## Install
13 |
14 | ```
15 | npm install ng2-adsense
16 | ```
17 |
18 | ## Dependencies
19 |
20 | Latest version available for each version of Angular
21 |
22 | | ng2-adsense | Angular |
23 | | ----------- | ----------- |
24 | | 5.4.3 | 5.x 6.x 7.x |
25 | | 6.0.3 | 8.x |
26 | | 8.0.1 | 9.x |
27 | | 9.1.0 | 10.x 11.x |
28 | | 10.1.0 | 12.x 13.x |
29 | | 11.0.0 | 14.x |
30 | | 12.0.0 | 15.x |
31 | | 13.0.0 | 16.x |
32 | | current | >=17 |
33 |
34 | ## Use
35 |
36 | #### Add adsense code
37 |
38 | Use the standard AdSense code somewhere in your `` as you [normally would](https://support.google.com/adsense/answer/7477845)
39 |
40 | ```html
41 |
42 | ```
43 |
44 | #### Import NgModule
45 |
46 | Add AdsenseModule to the imports of your NgModule
47 |
48 | ```typescript
49 | import { AdsenseModule } from 'ng2-adsense';
50 |
51 | @NgModule({
52 | imports: [
53 | // shown passing global defaults (optional)
54 | AdsenseModule.forRoot({
55 | adClient: 'ca-pub-7640562161899788',
56 | adSlot: 7259870550,
57 | }),
58 | ...
59 | ```
60 |
61 | #### Show Ad
62 |
63 | Uses global defaults which can be overriden via inputs
64 |
65 | ```html
66 |
67 | ```
68 |
69 | ## Inputs
70 |
71 | | input | type | description |
72 | | ------------------- | ------------- | --------------------------------------------------- |
73 | | adClient | string | account ca-pub-XXXXXXXXXXXXXXXX |
74 | | adSlot | string/number | ad slot/number |
75 | | adFormat | string | adsense ad format |
76 | | adRegion | string | older adsense code to make all ads on page the same |
77 | | display | string | element display style |
78 | | fullWidthResponsive | boolean | enable full width responsive ad |
79 | | height | number | element height in px |
80 | | width | number | element width in px |
81 | | layout | string | used for in-feed ads |
82 | | layoutKey | string | used for in-feed ads |
83 | | pageLevelAds | boolean | enable page-level ads |
84 | | adtest | string | sets up some sort of google test ad |
85 | | className | string | add custom class names to the "ins" element |
86 |
87 | ```html
88 |
95 | ```
96 |
--------------------------------------------------------------------------------
/src/app/page.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed, waitForAsync } from '@angular/core/testing';
2 |
3 | import { AdsenseModule } from '../lib/public_api';
4 |
5 | import {
6 | OtherPageComponent,
7 | PageComponent,
8 | } from './page.component';
9 |
10 | describe('PageComponent', () => {
11 | const options = {
12 | adClient: 'ca-pub-7640562161899788',
13 | adSlot: 2930227358,
14 | layout: 'z1',
15 | };
16 | beforeEach(() => {
17 | TestBed.configureTestingModule({
18 | imports: [AdsenseModule.forRoot(options)],
19 | declarations: [PageComponent],
20 | });
21 | TestBed.compileComponents();
22 | });
23 |
24 | it(
25 | 'should create the app',
26 | waitForAsync(() => {
27 | const fixture = TestBed.createComponent(PageComponent);
28 | const app = fixture.debugElement.componentInstance;
29 | expect(app).toBeTruthy();
30 | }),
31 | );
32 |
33 | it(
34 | `should have as title 'Page 1'`,
35 | waitForAsync(() => {
36 | const fixture = TestBed.createComponent(PageComponent);
37 | const app = fixture.debugElement.componentInstance;
38 | expect(app.title).toEqual('Page 1');
39 | }),
40 | );
41 |
42 | it(
43 | 'should render ng adsense',
44 | waitForAsync(() => {
45 | const fixture = TestBed.createComponent(PageComponent);
46 | fixture.detectChanges();
47 | const compiled = fixture.debugElement.nativeElement;
48 | const ad = compiled.querySelector('ins');
49 | expect(ad.className).toContain('adsbygoogle');
50 | expect(ad.getAttribute('data-ad-slot')).toEqual(String(options.adSlot));
51 | expect(ad.getAttribute('data-ad-client')).toEqual(options.adClient);
52 | expect(ad.getAttribute('data-layout')).toEqual('z1');
53 | expect(ad.getAttribute('data-layout-key')).toEqual(null);
54 | expect(ad.getAttribute('height')).toEqual(null);
55 | expect(ad.getAttribute('width')).toEqual(null);
56 | }),
57 | );
58 | });
59 |
60 | describe('OtherPageComponent', () => {
61 | const options = {
62 | adClient: 'ca-pub-7640562161899788',
63 | adSlot: 2930227358,
64 | heigth: 108,
65 | width: 320,
66 | };
67 | beforeEach(() => {
68 | TestBed.configureTestingModule({
69 | imports: [AdsenseModule.forRoot(options)],
70 | declarations: [OtherPageComponent],
71 | });
72 | TestBed.compileComponents();
73 | });
74 |
75 | it(
76 | 'should create the app',
77 | waitForAsync(() => {
78 | const fixture = TestBed.createComponent(OtherPageComponent);
79 | const app = fixture.debugElement.componentInstance;
80 | expect(app).toBeTruthy();
81 | }),
82 | );
83 |
84 | it(
85 | `should have as title 'Page 2'`,
86 | waitForAsync(() => {
87 | const fixture = TestBed.createComponent(OtherPageComponent);
88 | const app = fixture.debugElement.componentInstance;
89 | expect(app.title).toEqual('Page 2');
90 | }),
91 | );
92 |
93 | it(
94 | 'should render ng adsense',
95 | waitForAsync(() => {
96 | const fixture = TestBed.createComponent(OtherPageComponent);
97 | fixture.detectChanges();
98 | const compiled = fixture.debugElement.nativeElement;
99 | const ad = compiled.querySelector('ins');
100 | expect(ad.className).toContain('adsbygoogle');
101 | expect(ad.getAttribute('data-ad-slot')).toEqual(String(options.adSlot));
102 | expect(ad.getAttribute('data-ad-client')).toEqual(options.adClient);
103 | expect(ad.getAttribute('data-layout')).toEqual(null);
104 | expect(ad.getAttribute('data-layout-key')).toEqual(null);
105 | }),
106 | );
107 | });
108 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "ng2-adsense": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "style": "css"
11 | },
12 | "@schematics/angular:application": {
13 | "strict": true
14 | }
15 | },
16 | "root": "",
17 | "sourceRoot": "src",
18 | "prefix": "app",
19 | "architect": {
20 | "build": {
21 | "builder": "@angular-devkit/build-angular:browser",
22 | "options": {
23 | "outputPath": "dist",
24 | "index": "src/index.html",
25 | "main": "src/main.ts",
26 | "polyfills": "src/polyfills.ts",
27 | "tsConfig": "tsconfig.app.json",
28 | "assets": [
29 | "src/favicon.ico",
30 | "src/assets"
31 | ],
32 | "styles": [
33 | "src/styles.css"
34 | ],
35 | "scripts": [],
36 | "vendorChunk": true,
37 | "extractLicenses": false,
38 | "buildOptimizer": false,
39 | "sourceMap": true,
40 | "optimization": false,
41 | "namedChunks": true
42 | },
43 | "configurations": {
44 | "production": {
45 | "fileReplacements": [
46 | {
47 | "replace": "src/environments/environment.ts",
48 | "with": "src/environments/environment.prod.ts"
49 | }
50 | ],
51 | "optimization": true,
52 | "outputHashing": "all",
53 | "sourceMap": false,
54 | "namedChunks": false,
55 | "extractLicenses": true,
56 | "vendorChunk": false,
57 | "buildOptimizer": true,
58 | "budgets": [
59 | {
60 | "type": "initial",
61 | "maximumWarning": "500kb",
62 | "maximumError": "1mb"
63 | },
64 | {
65 | "type": "anyComponentStyle",
66 | "maximumWarning": "2kb",
67 | "maximumError": "4kb"
68 | }
69 | ]
70 | }
71 | }
72 | },
73 | "serve": {
74 | "builder": "@angular-devkit/build-angular:dev-server",
75 | "options": {
76 | "buildTarget": "ng2-adsense:build"
77 | },
78 | "configurations": {
79 | "production": {
80 | "buildTarget": "ng2-adsense:build:production"
81 | }
82 | }
83 | },
84 | "extract-i18n": {
85 | "builder": "@angular-devkit/build-angular:extract-i18n",
86 | "options": {
87 | "buildTarget": "ng2-adsense:build"
88 | }
89 | },
90 | "test": {
91 | "builder": "@angular-devkit/build-angular:karma",
92 | "options": {
93 | "main": "src/test.ts",
94 | "polyfills": "src/polyfills.ts",
95 | "tsConfig": "tsconfig.spec.json",
96 | "karmaConfig": "karma.conf.js",
97 | "assets": [
98 | "src/favicon.ico",
99 | "src/assets"
100 | ],
101 | "styles": [
102 | "src/styles.css"
103 | ],
104 | "scripts": []
105 | }
106 | },
107 | "lint": {
108 | "builder": "@angular-eslint/builder:lint",
109 | "options": {
110 | "lintFilePatterns": [
111 | "src/**/*.ts",
112 | "src/**/*.html"
113 | ]
114 | }
115 | }
116 | }
117 | }
118 | },
119 | "cli": {
120 | "analytics": false
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/src/lib/adsense.component.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AfterViewInit,
3 | ChangeDetectionStrategy,
4 | Component,
5 | Inject,
6 | Input,
7 | OnDestroy,
8 | OnInit,
9 | ViewChild,
10 | ElementRef,
11 | PLATFORM_ID,
12 | } from '@angular/core';
13 | import { isPlatformBrowser } from '@angular/common';
14 |
15 | import { ADSENSE_TOKEN, AdsenseConfig } from './adsense-config';
16 |
17 | @Component({
18 | selector: 'ng2-adsense,ng-adsense',
19 | template: `
20 |
35 |
36 | `,
37 | changeDetection: ChangeDetectionStrategy.OnPush,
38 | })
39 | export class AdsenseComponent implements OnInit, AfterViewInit, OnDestroy {
40 | /** adsense account ca-pub-XXXXXXXXXXXXXXXX */
41 | @Input() adClient!: string;
42 | /** ad slot/number */
43 | @Input() adSlot!: string | number;
44 | @Input() adFormat!: string;
45 | /** can be manually set if you need all ads on a page to have same id page-xxx */
46 | @Input() adRegion = 'page-' + Math.floor(Math.random() * 10000) + 1;
47 | /** ins element display style */
48 | @Input() display!: string;
49 | /** ins element height in px */
50 | @Input() width!: number;
51 | /** ins element width in px */
52 | @Input() height!: number;
53 | /** used for in-feed ads */
54 | @Input() layout!: string;
55 | /** used for in-feed ads */
56 | @Input() layoutKey!: string;
57 | /** enable page-level ads */
58 | @Input() pageLevelAds!: boolean;
59 | /** sets up some sort of google test ad */
60 | @Input() adtest!: string;
61 | /* used for flexible ads */
62 | @Input() fullWidthResponsive!: boolean;
63 | /**
64 | * classes applied to the ins element
65 | */
66 | @Input() className = '';
67 | @ViewChild('ins', { read: ElementRef, static: true }) ins!: ElementRef;
68 |
69 | constructor(
70 | @Inject(ADSENSE_TOKEN) private config: AdsenseConfig,
71 | @Inject(PLATFORM_ID) private platform: any,
72 | ) {}
73 |
74 | ngOnInit(): void {
75 | const config = this.config;
76 | this.adClient = this.adClient ?? config.adClient;
77 | this.adSlot = this.adSlot ?? config.adSlot;
78 | this.adFormat = this.adFormat ?? config.adFormat ?? 'auto';
79 | this.display = this.display ?? config.display ?? 'block';
80 | this.width = this.width ?? config.width;
81 | this.height = this.height ?? config.height;
82 | this.layout = this.layout ?? config.layout;
83 | this.layoutKey = this.layoutKey ?? config.layoutKey;
84 | this.pageLevelAds = this.pageLevelAds ?? config.pageLevelAds;
85 | this.adtest = this.adtest ?? config.adtest;
86 | this.fullWidthResponsive = this.fullWidthResponsive ?? config.fullWidthResponsive;
87 | }
88 | ngOnDestroy(): void {
89 | const iframe = this.ins.nativeElement.querySelector('iframe');
90 | if (iframe && iframe.contentWindow) {
91 | iframe.src = 'about:blank';
92 | iframe.remove();
93 | }
94 | }
95 |
96 | ngAfterViewInit(): void {
97 | if (isPlatformBrowser(this.platform)) {
98 | this.push();
99 | }
100 | }
101 |
102 | push(): void {
103 | const p: Record = {};
104 | if (this.pageLevelAds) {
105 | p.google_ad_client = this.adClient;
106 | p.enable_page_level_ads = true;
107 | }
108 |
109 | if (window) {
110 | try {
111 | ((window as any).adsbygoogle = (window as any).adsbygoogle || []).push(p);
112 | } catch {
113 | // pass
114 | }
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------