├── .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 |
12 | GitHub 17 |
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 |
    34 |
    35 |
    36 | 66 |
    67 |
    68 |
    69 | 70 |
    71 | 72 |
    73 |
    74 |
    75 | 76 |
    77 |
    78 | 79 | Demo using Angular {{ version.full }} 80 |
    81 | Not affiliated with Google AdSense 82 |
    83 |
    84 |
    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 | --------------------------------------------------------------------------------